From f4f22f4b50e0c0fea041708b7797ad36963608f5 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:05:58 +0400 Subject: [PATCH 01/77] [#379] Add `Di::has()` method to check for resolved instances in the container --- src/Di/Di.php | 8 +++++++ tests/Unit/Di/DiTest.php | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/Di/Di.php b/src/Di/Di.php index 03798c07..7cc49ba0 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -90,6 +90,14 @@ public static function isRegistered(string $abstract): bool return isset(self::$dependencies[$abstract]); } + /** + * Checks if an instance exists in the container + */ + public static function has(string $abstract): bool + { + return isset(self::$container[$abstract]); + } + /** * Sets an instance into container * @template T of object diff --git a/tests/Unit/Di/DiTest.php b/tests/Unit/Di/DiTest.php index e9197739..62c4759b 100644 --- a/tests/Unit/Di/DiTest.php +++ b/tests/Unit/Di/DiTest.php @@ -112,6 +112,57 @@ public function testDiIsRegistered(): void $this->assertTrue(Di::isRegistered(DummyService::class)); } + public function testDiHasReturnsFalseBeforeResolve(): void + { + Di::register(DummyService::class); + + $this->assertFalse(Di::has(DummyService::class)); + } + + public function testDiHasReturnsTrueAfterGet(): void + { + Di::register(DummyService::class); + + Di::get(DummyService::class); + + $this->assertTrue(Di::has(DummyService::class)); + } + + public function testDiHasReturnsTrueAfterSet(): void + { + $instance = new DummyService(); + + Di::set(DummyServiceInterface::class, $instance); + + $this->assertTrue(Di::has(DummyServiceInterface::class)); + } + + public function testDiHasReturnsFalseAfterResetContainer(): void + { + Di::register(DummyService::class); + + Di::get(DummyService::class); + + $this->assertTrue(Di::has(DummyService::class)); + + Di::resetContainer(); + + $this->assertFalse(Di::has(DummyService::class)); + } + + public function testDiHasReturnsFalseAfterReset(): void + { + Di::register(DummyService::class); + + Di::get(DummyService::class); + + $this->assertTrue(Di::has(DummyService::class)); + + Di::reset(); + + $this->assertFalse(Di::has(DummyService::class)); + } + public function testDiAbstractToConcreteBinding(): void { Di::register(DummyService::class, DummyServiceInterface::class); From 6c6108065ca6d3be0af05cf826707be418caf9b9 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:22:02 +0400 Subject: [PATCH 02/77] [#374] Define application execution boundary by calling Di::reset() in AppFactory Made-with: Cursor --- src/App/Factories/AppFactory.php | 3 +++ tests/Unit/App/Factories/AppFactoryTest.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index 79fa4c6d..0e6809ee 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -22,6 +22,7 @@ use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Enums\AppType; use Quantum\App\App; +use Quantum\Di\Di; /** * Class AppFactory @@ -69,6 +70,8 @@ private static function createInstance(string $type, string $baseDir): App throw AppException::adapterNotSupported($type); } + Di::reset(); + $adapterClass = self::ADAPTERS[$type]; App::setBaseDir($baseDir); diff --git a/tests/Unit/App/Factories/AppFactoryTest.php b/tests/Unit/App/Factories/AppFactoryTest.php index 7482cea6..5a5b657c 100644 --- a/tests/Unit/App/Factories/AppFactoryTest.php +++ b/tests/Unit/App/Factories/AppFactoryTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use Quantum\App\Enums\AppType; use Quantum\App\App; +use Quantum\Di\Di; class AppFactoryTest extends TestCase { @@ -77,4 +78,17 @@ public function testAppFactoryDestroy(): void $this->assertNotSame($app1, $app2); } + + public function testAppFactoryResetsContainerOnCreate(): void + { + AppFactory::destroy(AppType::WEB); + + Di::register(\stdClass::class); + + $this->assertTrue(Di::isRegistered(\stdClass::class)); + + AppFactory::create(AppType::WEB, PROJECT_ROOT); + + $this->assertFalse(Di::isRegistered(\stdClass::class)); + } } From a422a303ee79435f6d6fe4fcb5638a7b9a47bea5 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:26:35 +0400 Subject: [PATCH 03/77] [#378] Introduce AppContext class for application execution state Made-with: Cursor --- src/App/AppContext.php | 52 +++++++++++++++++++++++++++++++ tests/Unit/App/AppContextTest.php | 37 ++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/App/AppContext.php create mode 100644 tests/Unit/App/AppContextTest.php diff --git a/src/App/AppContext.php b/src/App/AppContext.php new file mode 100644 index 00000000..803a3e38 --- /dev/null +++ b/src/App/AppContext.php @@ -0,0 +1,52 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App; + +use Quantum\App\Enums\AppType; + +/** + * Class AppContext + * @package Quantum\App + */ +class AppContext +{ + private string $mode; + + public function __construct(string $mode) + { + if (!in_array($mode, [AppType::WEB, AppType::CONSOLE], true)) { + throw new \InvalidArgumentException("Invalid app mode: $mode"); + } + + $this->mode = $mode; + } + + public function getMode(): string + { + return $this->mode; + } + + public function isWebMode(): bool + { + return $this->mode === AppType::WEB; + } + + public function isConsoleMode(): bool + { + return $this->mode === AppType::CONSOLE; + } +} diff --git a/tests/Unit/App/AppContextTest.php b/tests/Unit/App/AppContextTest.php new file mode 100644 index 00000000..0ec7bece --- /dev/null +++ b/tests/Unit/App/AppContextTest.php @@ -0,0 +1,37 @@ +assertSame(AppType::WEB, $context->getMode()); + $this->assertTrue($context->isWebMode()); + $this->assertFalse($context->isConsoleMode()); + } + + public function testAppContextConsoleMode(): void + { + $context = new AppContext(AppType::CONSOLE); + + $this->assertSame(AppType::CONSOLE, $context->getMode()); + $this->assertFalse($context->isWebMode()); + $this->assertTrue($context->isConsoleMode()); + } + + public function testAppContextRejectsInvalidMode(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid app mode: invalid'); + + new AppContext('invalid'); + } +} From 993925380a45bd6c5ff8dc94bbc5b545e36ea98e Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:37:04 +0400 Subject: [PATCH 04/77] [#375] Introduce BootStageInterface and BootPipeline abstractions Made-with: Cursor --- src/App/BootPipeline.php | 55 ++++++++++++++ src/App/Contracts/BootStageInterface.php | 31 ++++++++ tests/Unit/App/BootPipelineTest.php | 96 ++++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 src/App/BootPipeline.php create mode 100644 src/App/Contracts/BootStageInterface.php create mode 100644 tests/Unit/App/BootPipelineTest.php diff --git a/src/App/BootPipeline.php b/src/App/BootPipeline.php new file mode 100644 index 00000000..6194838c --- /dev/null +++ b/src/App/BootPipeline.php @@ -0,0 +1,55 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App; + +use Quantum\App\Contracts\BootStageInterface; +use InvalidArgumentException; + +/** + * Class BootPipeline + * @package Quantum\App + */ +class BootPipeline +{ + /** + * @var BootStageInterface[] + */ + private array $stages; + + /** + * @param BootStageInterface[] $stages + */ + public function __construct(array $stages = []) + { + foreach ($stages as $stage) { + if (!$stage instanceof BootStageInterface) { + throw new InvalidArgumentException( + 'All stages must implement ' . BootStageInterface::class + ); + } + } + + $this->stages = $stages; + } + + public function run(AppContext $context): void + { + foreach ($this->stages as $stage) { + $stage->process($context); + } + } +} diff --git a/src/App/Contracts/BootStageInterface.php b/src/App/Contracts/BootStageInterface.php new file mode 100644 index 00000000..c907490a --- /dev/null +++ b/src/App/Contracts/BootStageInterface.php @@ -0,0 +1,31 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Contracts; + +use Quantum\App\AppContext; + +/** + * Interface BootStageInterface + * @package Quantum\App + */ +interface BootStageInterface +{ + /** + * Processes a single boot stage + */ + public function process(AppContext $context): void; +} diff --git a/tests/Unit/App/BootPipelineTest.php b/tests/Unit/App/BootPipelineTest.php new file mode 100644 index 00000000..2d66bdad --- /dev/null +++ b/tests/Unit/App/BootPipelineTest.php @@ -0,0 +1,96 @@ +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(AppType::WEB)); + + $this->assertSame(['first', 'second', 'third'], $log); + } + + public function testEmptyPipelineRunsWithoutError(): void + { + $pipeline = new BootPipeline([]); + $pipeline->run(new AppContext(AppType::WEB)); + + $this->assertTrue(true); + } + + public function testPipelinePassesContextToStages(): void + { + $receivedMode = null; + + $stage = $this->createStage(function (AppContext $context) use (&$receivedMode) { + $receivedMode = $context->getMode(); + }); + + $pipeline = new BootPipeline([$stage]); + $pipeline->run(new AppContext(AppType::CONSOLE)); + + $this->assertSame(AppType::CONSOLE, $receivedMode); + } + + public function testPipelinePropagatesException(): void + { + $stage = $this->createStage(function () { + throw new RuntimeException('Stage failed'); + }); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Stage failed'); + + $pipeline = new BootPipeline([$stage]); + $pipeline->run(new AppContext(AppType::WEB)); + } + + public function testPipelineRejectsInvalidStage(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('All stages must implement'); + + new BootPipeline([new \stdClass()]); + } + + private function createStage(callable $callback): BootStageInterface + { + return new class ($callback) implements BootStageInterface { + private $callback; + + public function __construct(callable $callback) + { + $this->callback = $callback; + } + + public function process(AppContext $context): void + { + ($this->callback)($context); + } + }; + } +} From 8047476958db8fc8225b5d533114f5001551384b Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:46:30 +0400 Subject: [PATCH 05/77] [#376] Extract RegisterCoreDependenciesStage from AppTrait Made-with: Cursor --- .../Stages/RegisterCoreDependenciesStage.php | 37 ++++++++++++++++++ .../RegisterCoreDependenciesStageTest.php | 39 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/App/Stages/RegisterCoreDependenciesStage.php create mode 100644 tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php diff --git a/src/App/Stages/RegisterCoreDependenciesStage.php b/src/App/Stages/RegisterCoreDependenciesStage.php new file mode 100644 index 00000000..fcb90233 --- /dev/null +++ b/src/App/Stages/RegisterCoreDependenciesStage.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\AppContext; +use Quantum\Di\Di; + +/** + * Class RegisterCoreDependenciesStage + * @package Quantum\App + */ +class RegisterCoreDependenciesStage implements BootStageInterface +{ + public function process(AppContext $context): void + { + $file = dirname(__DIR__) . DS . 'Config' . DS . 'dependencies.php'; + + $coreDependencies = (is_file($file) && is_array($deps = require $file)) ? $deps : []; + + Di::registerDependencies($coreDependencies); + } +} diff --git a/tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php b/tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php new file mode 100644 index 00000000..8ad8a683 --- /dev/null +++ b/tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php @@ -0,0 +1,39 @@ +assertFalse(Di::isRegistered(Loader::class)); + $this->assertFalse(Di::isRegistered(Request::class)); + $this->assertFalse(Di::isRegistered(Response::class)); + + $stage = new RegisterCoreDependenciesStage(); + $stage->process(new AppContext(AppType::WEB)); + + $this->assertTrue(Di::isRegistered(Loader::class)); + $this->assertTrue(Di::isRegistered(Request::class)); + $this->assertTrue(Di::isRegistered(Response::class)); + } +} From 23064add5f91afda6b193204235804f76e2b3581 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:47:09 +0400 Subject: [PATCH 06/77] [#376] Extract LoadHelpersStage from AppTrait Made-with: Cursor --- src/App/Stages/LoadHelpersStage.php | 63 +++++++++++++++++++ .../Unit/App/Stages/LoadHelpersStageTest.php | 40 ++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/App/Stages/LoadHelpersStage.php create mode 100644 tests/Unit/App/Stages/LoadHelpersStageTest.php diff --git a/src/App/Stages/LoadHelpersStage.php b/src/App/Stages/LoadHelpersStage.php new file mode 100644 index 00000000..5007e780 --- /dev/null +++ b/src/App/Stages/LoadHelpersStage.php @@ -0,0 +1,63 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\AppContext; +use Quantum\Loader\Loader; +use Quantum\App\App; +use Quantum\Di\Di; + +/** + * Class LoadHelpersStage + * @package Quantum\App + */ +class LoadHelpersStage implements BootStageInterface +{ + public function process(AppContext $context): void + { + $loader = Di::get(Loader::class); + + $this->loadComponentHelpers($loader); + $this->loadAppHelpers($loader); + $this->loadModuleHelpers($loader); + } + + private function loadComponentHelpers(Loader $loader): void + { + $srcDir = dirname(__DIR__, 2); + + $componentDirs = glob($srcDir . DS . '*', GLOB_ONLYDIR); + + foreach (is_array($componentDirs) ? $componentDirs : [] as $componentDir) { + $helperPath = $componentDir . DS . 'Helpers'; + if (is_dir($helperPath)) { + $loader->loadDir($helperPath); + } + } + } + + private function loadAppHelpers(Loader $loader): void + { + $loader->loadDir(App::getBaseDir() . DS . 'helpers'); + } + + private function loadModuleHelpers(Loader $loader): void + { + $loader->loadDir(App::getBaseDir() . DS . 'modules' . DS . '*' . DS . 'helpers'); + } +} diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php new file mode 100644 index 00000000..9a565b1c --- /dev/null +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -0,0 +1,40 @@ +process(new AppContext(AppType::WEB)); + } + + public function tearDown(): void + { + Di::reset(); + } + + public function testLoadHelpersStageLoadsComponentHelpers(): void + { + $this->assertFalse(function_exists('config')); + + $stage = new LoadHelpersStage(); + $stage->process(new AppContext(AppType::WEB)); + + $this->assertTrue(function_exists('config')); + $this->assertTrue(function_exists('env')); + $this->assertTrue(function_exists('base_dir')); + } +} From 82c224cc0135c84f9fb99e11f479c7dacf3bf6e3 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:49:42 +0400 Subject: [PATCH 07/77] [#376] Extract LoadEnvironmentStage from WebAppTrait and ConsoleAppTrait Made-with: Cursor --- src/App/Stages/LoadEnvironmentStage.php | 40 ++++++++++++++++ .../App/Stages/LoadEnvironmentStageTest.php | 47 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/App/Stages/LoadEnvironmentStage.php create mode 100644 tests/Unit/App/Stages/LoadEnvironmentStageTest.php diff --git a/src/App/Stages/LoadEnvironmentStage.php b/src/App/Stages/LoadEnvironmentStage.php new file mode 100644 index 00000000..97975f58 --- /dev/null +++ b/src/App/Stages/LoadEnvironmentStage.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Environment\Environment; +use Quantum\App\AppContext; +use Quantum\Loader\Setup; + +/** + * Class LoadEnvironmentStage + * @package Quantum\App + */ +class LoadEnvironmentStage implements BootStageInterface +{ + public function process(AppContext $context): void + { + $environment = Environment::getInstance(); + + if ($context->isConsoleMode()) { + $environment->setMutable(true); + } + + $environment->load(new Setup('config', 'env')); + } +} diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php new file mode 100644 index 00000000..0f9b0578 --- /dev/null +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -0,0 +1,47 @@ +process($context); + (new LoadHelpersStage())->process($context); + } + + public function tearDown(): void + { + Di::reset(); + } + + public function testLoadEnvironmentStageLoadsEnvVars(): void + { + $stage = new LoadEnvironmentStage(); + $stage->process(new AppContext(AppType::WEB)); + + $this->assertNotEmpty(env('APP_KEY')); + } + + public function testLoadEnvironmentStageSetsMutableForConsole(): void + { + $stage = new LoadEnvironmentStage(); + $stage->process(new AppContext(AppType::CONSOLE)); + + $this->assertNotEmpty(env('APP_KEY')); + } +} From b596f87f87fd0abf74d42239c210151c92dc2b15 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:52:54 +0400 Subject: [PATCH 08/77] [#376] Extract LoadAppConfigStage from AppTrait Made-with: Cursor --- src/App/Stages/LoadAppConfigStage.php | 36 ++++++++++++ .../App/Stages/LoadAppConfigStageTest.php | 58 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/App/Stages/LoadAppConfigStage.php create mode 100644 tests/Unit/App/Stages/LoadAppConfigStageTest.php diff --git a/src/App/Stages/LoadAppConfigStage.php b/src/App/Stages/LoadAppConfigStage.php new file mode 100644 index 00000000..de0c8755 --- /dev/null +++ b/src/App/Stages/LoadAppConfigStage.php @@ -0,0 +1,36 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\AppContext; +use Quantum\Config\Config; +use Quantum\Loader\Setup; + +/** + * Class LoadAppConfigStage + * @package Quantum\App + */ +class LoadAppConfigStage implements BootStageInterface +{ + public function process(AppContext $context): void + { + if (!config()->has('app')) { + Config::getInstance()->import(new Setup('config', 'app')); + } + } +} diff --git a/tests/Unit/App/Stages/LoadAppConfigStageTest.php b/tests/Unit/App/Stages/LoadAppConfigStageTest.php new file mode 100644 index 00000000..259fc84b --- /dev/null +++ b/tests/Unit/App/Stages/LoadAppConfigStageTest.php @@ -0,0 +1,58 @@ +process($context); + (new LoadHelpersStage())->process($context); + (new LoadEnvironmentStage())->process($context); + } + + public function tearDown(): void + { + config()->flush(); + Di::reset(); + } + + public function testLoadAppConfigStageImportsAppConfig(): void + { + $this->assertFalse(config()->has('app')); + + $stage = new LoadAppConfigStage(); + $stage->process(new AppContext(AppType::WEB)); + + $this->assertTrue(config()->has('app')); + } + + public function testLoadAppConfigStageSkipsIfAlreadyLoaded(): void + { + $stage = new LoadAppConfigStage(); + $context = new AppContext(AppType::WEB); + + $stage->process($context); + + $this->assertTrue(config()->has('app')); + + $stage->process($context); + + $this->assertTrue(config()->has('app')); + } +} From 0a62026ebf6dccd0cfe3fcac806e364587daff50 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:56:08 +0400 Subject: [PATCH 09/77] [#376] Extract SetupErrorHandlerStage from AppTrait Made-with: Cursor --- src/App/Stages/SetupErrorHandlerStage.php | 34 +++++++++++++ .../App/Stages/SetupErrorHandlerStageTest.php | 50 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/App/Stages/SetupErrorHandlerStage.php create mode 100644 tests/Unit/App/Stages/SetupErrorHandlerStageTest.php diff --git a/src/App/Stages/SetupErrorHandlerStage.php b/src/App/Stages/SetupErrorHandlerStage.php new file mode 100644 index 00000000..b4d441a5 --- /dev/null +++ b/src/App/Stages/SetupErrorHandlerStage.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Logger\Factories\LoggerFactory; +use Quantum\Tracer\ErrorHandler; +use Quantum\App\AppContext; + +/** + * Class SetupErrorHandlerStage + * @package Quantum\App + */ +class SetupErrorHandlerStage implements BootStageInterface +{ + public function process(AppContext $context): void + { + ErrorHandler::getInstance()->setup(LoggerFactory::get()); + } +} diff --git a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php new file mode 100644 index 00000000..14148cc1 --- /dev/null +++ b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php @@ -0,0 +1,50 @@ +process($context); + (new LoadHelpersStage())->process($context); + (new LoadEnvironmentStage())->process($context); + (new LoadAppConfigStage())->process($context); + } + + public function tearDown(): void + { + restore_error_handler(); + restore_exception_handler(); + config()->flush(); + Di::reset(); + } + + public function testSetupErrorHandlerStageRegistersHandlers(): void + { + $stage = new SetupErrorHandlerStage(); + $stage->process(new AppContext(AppType::WEB)); + + $errorHandler = set_error_handler(function () { + }); + restore_error_handler(); + + $this->assertNotNull($errorHandler); + } +} From 7eea2e732db98bd0cabb08bbb3ac751cfe200959 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:00:08 +0400 Subject: [PATCH 10/77] [#376] Extract LoadLanguageStage from AppTrait Made-with: Cursor --- src/App/Stages/LoadLanguageStage.php | 37 ++++++++++++++++ .../Unit/App/Stages/LoadLanguageStageTest.php | 44 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/App/Stages/LoadLanguageStage.php create mode 100644 tests/Unit/App/Stages/LoadLanguageStageTest.php diff --git a/src/App/Stages/LoadLanguageStage.php b/src/App/Stages/LoadLanguageStage.php new file mode 100644 index 00000000..c0568857 --- /dev/null +++ b/src/App/Stages/LoadLanguageStage.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Lang\Factories\LangFactory; +use Quantum\App\AppContext; + +/** + * Class LoadLanguageStage + * @package Quantum\App + */ +class LoadLanguageStage implements BootStageInterface +{ + public function process(AppContext $context): void + { + $lang = LangFactory::get(); + + if ($lang->isEnabled()) { + $lang->load(); + } + } +} diff --git a/tests/Unit/App/Stages/LoadLanguageStageTest.php b/tests/Unit/App/Stages/LoadLanguageStageTest.php new file mode 100644 index 00000000..4d4dd233 --- /dev/null +++ b/tests/Unit/App/Stages/LoadLanguageStageTest.php @@ -0,0 +1,44 @@ +process($context); + (new LoadHelpersStage())->process($context); + (new LoadEnvironmentStage())->process($context); + (new LoadAppConfigStage())->process($context); + } + + public function tearDown(): void + { + config()->flush(); + Di::reset(); + } + + public function testLoadLanguageStageRunsWithoutError(): void + { + $stage = new LoadLanguageStage(); + $stage->process(new AppContext(AppType::WEB)); + + $this->assertTrue(true); + } +} From bcb9a2f69bad2e0c9798423a72b14efeb6c46fb8 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:06:25 +0400 Subject: [PATCH 11/77] [#377] Wire boot pipeline into adapters, replace trait calls with stages Made-with: Cursor --- src/App/Adapters/AppAdapter.php | 18 +++------- src/App/Adapters/ConsoleAppAdapter.php | 36 +++++++++++-------- src/App/Adapters/WebAppAdapter.php | 27 +++++++++----- .../Unit/App/Stages/LoadHelpersStageTest.php | 2 -- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/App/Adapters/AppAdapter.php b/src/App/Adapters/AppAdapter.php index db9cd43f..446ff7e0 100644 --- a/src/App/Adapters/AppAdapter.php +++ b/src/App/Adapters/AppAdapter.php @@ -16,11 +16,9 @@ namespace Quantum\App\Adapters; -use Quantum\App\Exceptions\BaseException; use Quantum\App\Contracts\AppInterface; -use Quantum\Di\Exceptions\DiException; use Quantum\App\Traits\AppTrait; -use ReflectionException; +use Quantum\App\AppContext; /** * Class AppAdapter @@ -32,16 +30,10 @@ abstract class AppAdapter implements AppInterface private static string $baseDir; - /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - */ - public function __construct() + protected AppContext $context; + + public function __construct(string $mode) { - $this->loadCoreDependencies(); - $this->loadComponentHelperFunctions(); - $this->loadAppHelperFunctions(); - $this->loadModuleHelperFunctions(); + $this->context = new AppContext($mode); } } diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index 4d2875f5..c9692a87 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -16,16 +16,19 @@ namespace Quantum\App\Adapters; +use Quantum\App\Stages\RegisterCoreDependenciesStage; use Symfony\Component\Console\Output\ConsoleOutput; use Quantum\App\Exceptions\StopExecutionException; -use Quantum\Environment\Exceptions\EnvException; use Symfony\Component\Console\Input\ArgvInput; -use Quantum\Config\Exceptions\ConfigException; +use Quantum\App\Stages\SetupErrorHandlerStage; +use Quantum\App\Stages\LoadEnvironmentStage; use Symfony\Component\Console\Application; -use Quantum\Lang\Exceptions\LangException; -use Quantum\App\Exceptions\BaseException; +use Quantum\App\Stages\LoadAppConfigStage; +use Quantum\App\Stages\LoadLanguageStage; +use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Traits\ConsoleAppTrait; -use Quantum\Di\Exceptions\DiException; +use Quantum\App\Enums\AppType; +use Quantum\App\BootPipeline; use ReflectionException; if (!defined('DS')) { @@ -48,18 +51,26 @@ class ConsoleAppAdapter extends AppAdapter public function __construct() { - parent::__construct(); + parent::__construct(AppType::CONSOLE); $this->input = new ArgvInput(); $this->output = new ConsoleOutput(); $commandName = $this->input->getFirstArgument(); + $stages = [ + new RegisterCoreDependenciesStage(), + new LoadHelpersStage(), + ]; + if ($commandName !== 'core:env') { - $this->loadEnvironment(); - $this->loadAppConfig(); + $stages[] = new LoadEnvironmentStage(); + $stages[] = new LoadAppConfigStage(); } + $pipeline = new BootPipeline($stages); + $pipeline->run($this->context); + $this->application = $this->createApplication( config()->get('app.name', 'UNKNOWN'), config()->get('app.version', 'UNKNOWN') @@ -67,22 +78,17 @@ public function __construct() } /** - * @throws DiException - * @throws EnvException - * @throws BaseException - * @throws ConfigException - * @throws LangException * @throws ReflectionException */ public function start(): ?int { try { - $this->loadLanguage(); + (new LoadLanguageStage())->process($this->context); $this->registerCoreCommands(); $this->registerAppCommands(); - $this->setupErrorHandler(); + (new SetupErrorHandlerStage())->process($this->context); $this->validateCommand(); diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 9dc0cf61..e1267c4b 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -17,18 +17,23 @@ namespace Quantum\App\Adapters; use Quantum\Middleware\Exceptions\MiddlewareException; +use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\Database\Exceptions\DatabaseException; use Quantum\App\Exceptions\StopExecutionException; use Quantum\Session\Exceptions\SessionException; -use Quantum\Environment\Exceptions\EnvException; use Quantum\Module\Exceptions\ModuleException; use Quantum\Config\Exceptions\ConfigException; +use Quantum\App\Stages\SetupErrorHandlerStage; use Quantum\Router\Exceptions\RouteException; +use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\Http\Exceptions\HttpException; use Quantum\Csrf\Exceptions\CsrfException; use Quantum\Lang\Exceptions\LangException; +use Quantum\App\Stages\LoadAppConfigStage; use Quantum\Middleware\MiddlewareManager; +use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Exceptions\BaseException; +use Quantum\App\Stages\LoadHelpersStage; use Quantum\Di\Exceptions\DiException; use Quantum\App\Traits\WebAppTrait; use Quantum\Router\RouteCollection; @@ -38,6 +43,8 @@ use DebugBar\DebugBarException; use Quantum\Router\RouteFinder; use Quantum\Debugger\Debugger; +use Quantum\App\Enums\AppType; +use Quantum\App\BootPipeline; use Quantum\Hook\HookManager; use Quantum\Http\Response; use Quantum\Http\Request; @@ -64,17 +71,21 @@ class WebAppAdapter extends AppAdapter /** * @throws BaseException - * @throws ConfigException * @throws DiException - * @throws EnvException * @throws ReflectionException */ public function __construct() { - parent::__construct(); + parent::__construct(AppType::WEB); + + $pipeline = new BootPipeline([ + new RegisterCoreDependenciesStage(), + new LoadHelpersStage(), + new LoadEnvironmentStage(), + new LoadAppConfigStage(), + ]); - $this->loadEnvironment(); - $this->loadAppConfig(); + $pipeline->run($this->context); $this->request = Di::get(Request::class); $this->response = Di::get(Response::class); @@ -105,7 +116,7 @@ public function start(): ?int stop(); } - $this->setupErrorHandler(); + (new SetupErrorHandlerStage())->process($this->context); $this->initializeDebugger(); $moduleLoader = ModuleLoader::getInstance(); @@ -130,7 +141,7 @@ public function start(): ?int $this->request->setMatchedRoute($matchedRoute); - $this->loadLanguage(); + (new LoadLanguageStage())->process($this->context); $debugger = Debugger::getInstance(); if ($debugger->isEnabled()) { diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php index 9a565b1c..730b52ed 100644 --- a/tests/Unit/App/Stages/LoadHelpersStageTest.php +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -28,8 +28,6 @@ public function tearDown(): void public function testLoadHelpersStageLoadsComponentHelpers(): void { - $this->assertFalse(function_exists('config')); - $stage = new LoadHelpersStage(); $stage->process(new AppContext(AppType::WEB)); From df4fb0065c4bc4d922093a881de2afdf6fd5ff45 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:27:58 +0400 Subject: [PATCH 12/77] [#377] Remove deprecated boot methods from AppTrait, WebAppTrait, ConsoleAppTrait Made-with: Cursor --- src/App/Traits/AppTrait.php | 111 ----------------------------- src/App/Traits/ConsoleAppTrait.php | 18 ----- src/App/Traits/WebAppTrait.php | 13 ---- 3 files changed, 142 deletions(-) diff --git a/src/App/Traits/AppTrait.php b/src/App/Traits/AppTrait.php index 620cb44c..dd0c8470 100644 --- a/src/App/Traits/AppTrait.php +++ b/src/App/Traits/AppTrait.php @@ -16,21 +16,6 @@ namespace Quantum\App\Traits; -use Quantum\Config\Exceptions\ConfigException; -use Quantum\Loader\Exceptions\LoaderException; -use Quantum\Logger\Factories\LoggerFactory; -use Quantum\Lang\Exceptions\LangException; -use Quantum\App\Exceptions\BaseException; -use Quantum\Lang\Factories\LangFactory; -use Quantum\Di\Exceptions\DiException; -use Quantum\Tracer\ErrorHandler; -use Quantum\Loader\Loader; -use Quantum\Config\Config; -use Quantum\Loader\Setup; -use ReflectionException; -use Quantum\App\App; -use Quantum\Di\Di; - /** * Class AppTrait * @package Quantum\App @@ -52,100 +37,4 @@ public static function getBaseDir(): string { return self::$baseDir; } - - /** - * @throws DiException - */ - protected function loadCoreDependencies(): void - { - $file = dirname(__DIR__) . DS . 'Config' . DS . 'dependencies.php'; - - $coreDependencies = (is_file($file) && is_array($deps = require $file)) ? $deps : []; - - Di::registerDependencies($coreDependencies); - } - - /** - * Loads component helper functions - * @throws DiException - * @throws ReflectionException - */ - protected function loadComponentHelperFunctions(): void - { - $loader = Di::get(Loader::class); - - $srcDir = dirname(__DIR__, 2); - - $componentDirs = glob($srcDir . DS . '*', GLOB_ONLYDIR); - - foreach (is_array($componentDirs) ? $componentDirs : [] as $componentDir) { - $helperPath = $componentDir . DS . 'Helpers'; - if (is_dir($helperPath)) { - $loader->loadDir($helperPath); - } - } - } - - /** - * Loads app helper functions - * @throws DiException - * @throws ReflectionException - */ - protected function loadAppHelperFunctions(): void - { - $loader = Di::get(Loader::class); - $loader->loadDir(App::getBaseDir() . DS . 'helpers'); - } - - /** - * Loads module helper functions - * @throws DiException - * @throws ReflectionException - */ - protected function loadModuleHelperFunctions(): void - { - $loader = Di::get(Loader::class); - $loader->loadDir(App::getBaseDir() . DS . 'modules' . DS . '*' . DS . 'helpers'); - } - - /** - * @return void - * @throws DiException - * @throws ReflectionException - * @throws ConfigException|LoaderException - */ - protected function loadAppConfig() - { - if (!config()->has('app')) { - Config::getInstance()->import(new Setup('config', 'app')); - } - } - - /** - * @return void - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - */ - protected function setupErrorHandler() - { - ErrorHandler::getInstance()->setup(LoggerFactory::get()); - } - - /** - * @return void - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws LangException - */ - protected function loadLanguage() - { - $lang = LangFactory::get(); - - if ($lang->isEnabled()) { - $lang->load(); - } - } } diff --git a/src/App/Traits/ConsoleAppTrait.php b/src/App/Traits/ConsoleAppTrait.php index 85c2be9b..b8207b66 100644 --- a/src/App/Traits/ConsoleAppTrait.php +++ b/src/App/Traits/ConsoleAppTrait.php @@ -16,14 +16,9 @@ namespace Quantum\App\Traits; -use Quantum\Environment\Exceptions\EnvException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Application; -use Quantum\App\Exceptions\BaseException; -use Quantum\Di\Exceptions\DiException; use Quantum\Console\CommandDiscovery; -use Quantum\Environment\Environment; -use Quantum\Loader\Setup; use ReflectionException; use Exception; @@ -33,19 +28,6 @@ */ trait ConsoleAppTrait { - /** - * @throws DiException - * @throws ReflectionException - * @throws EnvException - * @throws BaseException - */ - protected function loadEnvironment(): void - { - Environment::getInstance() - ->setMutable(true) - ->load(new Setup('config', 'env')); - } - public function createApplication(string $name, string $version): Application { return new Application($name, $version); diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index f41922b8..d7155d32 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -16,14 +16,12 @@ namespace Quantum\App\Traits; -use Quantum\Environment\Exceptions\EnvException; use Quantum\Config\Exceptions\ConfigException; use Quantum\Loader\Exceptions\LoaderException; use Quantum\Http\Exceptions\HttpException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; -use Quantum\Environment\Environment; use DebugBar\DebugBarException; use Quantum\Environment\Server; use Quantum\Debugger\Debugger; @@ -38,17 +36,6 @@ */ trait WebAppTrait { - /** - * @throws DiException - * @throws ReflectionException - * @throws EnvException - * @throws BaseException - */ - protected function loadEnvironment(): void - { - Environment::getInstance()->load(new Setup('config', 'env')); - } - /** * @throws BaseException * @throws DiException From 135dc23fad32ffad44a3aad8d6599a02e17835cf Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:06:07 +0400 Subject: [PATCH 13/77] [#380] Register Config as DI dependency and route all access through DI Made-with: Cursor --- src/App/Config/dependencies.php | 1 + src/App/Stages/LoadAppConfigStage.php | 3 +- src/Config/Helpers/config.php | 3 +- tests/Unit/AppTestCase.php | 6 +- tests/Unit/Config/ConfigTest.php | 92 ++++++++++++--------------- 5 files changed, 49 insertions(+), 56 deletions(-) diff --git a/src/App/Config/dependencies.php b/src/App/Config/dependencies.php index 9b8a91fa..918a0af4 100644 --- a/src/App/Config/dependencies.php +++ b/src/App/Config/dependencies.php @@ -4,4 +4,5 @@ \Quantum\Loader\Loader::class => \Quantum\Loader\Loader::class, \Quantum\Http\Request::class => \Quantum\Http\Request::class, \Quantum\Http\Response::class => \Quantum\Http\Response::class, + \Quantum\Config\Config::class => \Quantum\Config\Config::class, ]; diff --git a/src/App/Stages/LoadAppConfigStage.php b/src/App/Stages/LoadAppConfigStage.php index de0c8755..a23c6612 100644 --- a/src/App/Stages/LoadAppConfigStage.php +++ b/src/App/Stages/LoadAppConfigStage.php @@ -18,7 +18,6 @@ use Quantum\App\Contracts\BootStageInterface; use Quantum\App\AppContext; -use Quantum\Config\Config; use Quantum\Loader\Setup; /** @@ -30,7 +29,7 @@ class LoadAppConfigStage implements BootStageInterface public function process(AppContext $context): void { if (!config()->has('app')) { - Config::getInstance()->import(new Setup('config', 'app')); + config()->import(new Setup('config', 'app')); } } } diff --git a/src/Config/Helpers/config.php b/src/Config/Helpers/config.php index 877f549c..b9ae41bb 100644 --- a/src/Config/Helpers/config.php +++ b/src/Config/Helpers/config.php @@ -13,11 +13,12 @@ */ use Quantum\Config\Config; +use Quantum\Di\Di; /** * Config facade */ function config(): Config { - return Config::getInstance(); + return Di::get(Config::class); } diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 89893cdb..af93aeed 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Quantum\Debugger\Debugger; use Quantum\App\Enums\AppType; +use Quantum\Config\Config; use Quantum\Router\Route; use Quantum\Http\Request; use Quantum\Loader\Setup; @@ -36,7 +37,10 @@ public function tearDown(): void Request::flush(); AppFactory::destroy(AppType::WEB); - config()->flush(); + + if (Di::isRegistered(Config::class)) { + config()->flush(); + } Debugger::getInstance()->resetStore(); Di::reset(); } diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php index 68315e2f..157ffc3e 100644 --- a/tests/Unit/Config/ConfigTest.php +++ b/tests/Unit/Config/ConfigTest.php @@ -11,39 +11,39 @@ class ConfigTest extends AppTestCase { + private Config $config; + public function setUp(): void { parent::setUp(); config()->flush(); + + $this->config = new Config(); } public function testConfigLoad(): void { - $config = Config::getInstance(); - - $this->assertEmpty($config->all()); + $this->assertEmpty($this->config->all()); - $config->load(new Setup('config', 'app')); + $this->config->load(new Setup('config', 'app')); - $this->assertNotEmpty($config->all()); + $this->assertNotEmpty($this->config->all()); - $this->assertInstanceOf(Data::class, $config->all()); + $this->assertInstanceOf(Data::class, $this->config->all()); } public function testConfigImport(): void { - $config = Config::getInstance(); + $this->config->load(new Setup('config', 'app')); - $config->load(new Setup('config', 'app')); + $this->assertNull($this->config->get('database.default')); - $this->assertNull($config->get('database.default')); + $this->config->import(new Setup('config', 'database')); - $config->import(new Setup('config', 'database')); + $this->assertNotNull($this->config->get('database.default')); - $this->assertNotNull($config->get('database.default')); - - $this->assertEquals('sqlite', $config->get('database.default')); + $this->assertEquals('sqlite', $this->config->get('database.default')); } public function testImportingNonExistingConfigFile(): void @@ -52,92 +52,80 @@ public function testImportingNonExistingConfigFile(): void $this->expectExceptionMessage('File `config' . DS . 'somefile` not found!'); - Config::getInstance()->import(new Setup('config', 'somefile')); + $this->config->import(new Setup('config', 'somefile')); } public function testCollisionAtImporting(): void { - $config = Config::getInstance(); - - $config->import(new Setup('config', 'app')); + $this->config->import(new Setup('config', 'app')); $this->expectException(ConfigException::class); $this->expectExceptionMessage('Config key `app` is already in use'); - $config->import(new Setup('config', 'app')); + $this->config->import(new Setup('config', 'app')); } public function testConfigHas(): void { - $config = Config::getInstance(); + $this->config->import(new Setup('config', 'app')); - $config->import(new Setup('config', 'app')); + $this->assertTrue($this->config->has('app.debug')); - $this->assertTrue($config->has('app.debug')); + $this->assertTrue($this->config->has('app.test')); - $this->assertTrue($config->has('app.test')); - - $this->assertFalse($config->has('app.none')); + $this->assertFalse($this->config->has('app.none')); } public function testConfigGet(): void { - $config = Config::getInstance(); - - $config->import(new Setup('config', 'lang')); + $this->config->import(new Setup('config', 'lang')); - $this->assertIsArray($config->get('lang.supported')); + $this->assertIsArray($this->config->get('lang.supported')); - $this->assertEquals('Default Value', $config->get('not-exists', 'Default Value')); + $this->assertEquals('Default Value', $this->config->get('not-exists', 'Default Value')); - $this->assertNull($config->get('not-exists')); + $this->assertNull($this->config->get('not-exists')); } public function testConfigSet(): void { - $config = Config::getInstance(); - - $this->assertFalse($config->has('new-value')); + $this->assertFalse($this->config->has('new-value')); - $config->set('new-value', 'New Value'); + $this->config->set('new-value', 'New Value'); - $this->assertTrue($config->has('new-value')); + $this->assertTrue($this->config->has('new-value')); - $this->assertEquals('New Value', $config->get('new-value')); + $this->assertEquals('New Value', $this->config->get('new-value')); - $config->set('other.nested', 'Nested Value'); + $this->config->set('other.nested', 'Nested Value'); - $this->assertTrue($config->has('other.nested')); + $this->assertTrue($this->config->has('other.nested')); - $this->assertEquals('Nested Value', $config->get('other.nested')); + $this->assertEquals('Nested Value', $this->config->get('other.nested')); } public function testConfigDelete(): void { - $config = Config::getInstance(); + $this->config->import(new Setup('config', 'app')); - $config->import(new Setup('config', 'app')); + $this->assertNotNull($this->config->get('app.test')); - $this->assertNotNull($config->get('app.test')); + $this->config->delete('app.test'); - $config->delete('app.test'); + $this->assertFalse($this->config->has('app.test')); - $this->assertFalse($config->has('app.test')); - - $this->assertNull($config->get('app.test')); + $this->assertNull($this->config->get('app.test')); } public function testConfigFlush(): void { - $config = Config::getInstance(); - - $config->import(new Setup('config', 'app')); + $this->config->import(new Setup('config', 'app')); - $this->assertNotEmpty($config->all()); + $this->assertNotEmpty($this->config->all()); - $config->flush(); + $this->config->flush(); - $this->assertEmpty($config->all()); + $this->assertEmpty($this->config->all()); } } From 8f0a41fc20cc71b6c8ce0ea6b416bbfa0ec2bd6f Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:17:14 +0400 Subject: [PATCH 14/77] [#380] Remove Config singleton, convert static state to instance properties Made-with: Cursor --- src/Config/Config.php | 44 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/Config/Config.php b/src/Config/Config.php index c2379420..6657912b 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -32,21 +32,7 @@ */ class Config implements ConfigInterface { - private static ?Data $configs = null; - - private static ?Config $instance = null; - - /** - * GetInstance - */ - public static function getInstance(): Config - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } + private ?Data $configs = null; /** * @inheritDoc @@ -54,11 +40,11 @@ public static function getInstance(): Config */ public function load(Setup $setup): void { - if (self::$configs !== null) { + if ($this->configs !== null) { return; } - self::$configs = new Data(Di::get(Loader::class)->setup($setup)->load()); + $this->configs = new Data(Di::get(Loader::class)->setup($setup)->load()); } /** @@ -75,10 +61,10 @@ public function import(Setup $setup): void throw ConfigException::configCollision($fileName); } - if (!self::$configs) { - self::$configs = new Data([$fileName => Di::get(Loader::class)->setup($setup)->load()]); + if (!$this->configs) { + $this->configs = new Data([$fileName => Di::get(Loader::class)->setup($setup)->load()]); } else { - self::$configs->import([$fileName => Di::get(Loader::class)->setup($setup)->load()]); + $this->configs->import([$fileName => Di::get(Loader::class)->setup($setup)->load()]); } } @@ -87,8 +73,8 @@ public function import(Setup $setup): void */ public function get(string $key, $default = null) { - if (self::$configs && self::$configs->has($key)) { - return self::$configs->get($key); + if ($this->configs && $this->configs->has($key)) { + return $this->configs->get($key); } return $default; @@ -99,7 +85,7 @@ public function get(string $key, $default = null) */ public function all(): ?Data { - return self::$configs; + return $this->configs; } /** @@ -107,7 +93,7 @@ public function all(): ?Data */ public function has(string $key): bool { - return self::$configs && !empty(self::$configs->has($key)); + return $this->configs && !empty($this->configs->has($key)); } /** @@ -115,10 +101,10 @@ public function has(string $key): bool */ public function set(string $key, $value): void { - if (!self::$configs) { - self::$configs = new Data([$key => $value]); + if (!$this->configs) { + $this->configs = new Data([$key => $value]); } else { - self::$configs->set($key, $value); + $this->configs->set($key, $value); } } @@ -127,7 +113,7 @@ public function set(string $key, $value): void */ public function delete(string $key): void { - self::$configs && self::$configs->remove($key); + $this->configs && $this->configs->remove($key); } /** @@ -135,6 +121,6 @@ public function delete(string $key): void */ public function flush(): void { - self::$configs = null; + $this->configs = null; } } From de4713f85dfd4971f16b81d3f84ef96ce8f092ca Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:50:11 +0400 Subject: [PATCH 15/77] [#381] Enhance Di::get() to auto-register instantiable concrete classes Made-with: Cursor --- src/Di/Di.php | 5 ++++- tests/Unit/Di/DiTest.php | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Di/Di.php b/src/Di/Di.php index 7cc49ba0..8d2ecb4d 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -141,7 +141,10 @@ public static function set(string $abstract, object $instance, bool $override = public static function get(string $dependency, array $args = []) { if (!self::isRegistered($dependency)) { - throw DiException::dependencyNotRegistered($dependency); + if (!self::instantiable($dependency)) { + throw DiException::dependencyNotRegistered($dependency); + } + self::register($dependency); } return self::resolve($dependency, $args, true); diff --git a/tests/Unit/Di/DiTest.php b/tests/Unit/Di/DiTest.php index 62c4759b..a42ec741 100644 --- a/tests/Unit/Di/DiTest.php +++ b/tests/Unit/Di/DiTest.php @@ -253,15 +253,23 @@ public function testDiGetCoreDependencies(): void $this->assertInstanceOf(Response::class, Di::get(Response::class)); } - public function testDiAttemptingToGetNotRegisteredDependency(): void + public function testDiGetAutoRegistersInstantiableClass(): void { - $this->assertInstanceOf(Loader::class, Di::get(Loader::class)); + $this->assertFalse(Di::isRegistered(DiException::class)); + + $instance = Di::get(DiException::class); + $this->assertInstanceOf(DiException::class, $instance); + $this->assertTrue(Di::isRegistered(DiException::class)); + } + + public function testDiGetThrowsForNonInstantiableClass(): void + { $this->expectException(DiException::class); - $this->expectExceptionMessage('The dependency `Quantum\Di\Exceptions\DiException` is not registered.'); + $this->expectExceptionMessage('The dependency `Quantum\Service\DummyServiceInterface` is not registered.'); - Di::get(DiException::class); + Di::get(DummyServiceInterface::class); } public function testDiCircularDependencyDetectedAtResolve(): void From c5246d861060e7eeea9e83165a1bf589ce899be0 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:54:27 +0400 Subject: [PATCH 16/77] [#381] Migrate LangFactory from static singleton to DI-managed instance Made-with: Cursor --- src/Lang/Factories/LangFactory.php | 42 +++++++++++-------- tests/Unit/Lang/Factories/LangFactoryTest.php | 13 ++++-- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/Lang/Factories/LangFactory.php b/src/Lang/Factories/LangFactory.php index f176b5c6..36804ce4 100644 --- a/src/Lang/Factories/LangFactory.php +++ b/src/Lang/Factories/LangFactory.php @@ -24,6 +24,7 @@ use Quantum\Loader\Setup; use ReflectionException; use Quantum\Lang\Lang; +use Quantum\Di\Di; /** * Class LangFactory @@ -31,10 +32,18 @@ */ class LangFactory { + private ?Lang $instance = null; + /** - * @var Lang|null Cached Lang instance + * @throws ConfigException + * @throws LangException + * @throws DiException + * @throws ReflectionException */ - private static ?Lang $instance = null; + public static function get(): Lang + { + return Di::get(self::class)->resolve(); + } /** * @throws ConfigException @@ -42,20 +51,19 @@ class LangFactory * @throws DiException * @throws ReflectionException */ - public static function get(): Lang + public function resolve(): Lang { - if (self::$instance !== null) { - return self::$instance; + if ($this->instance !== null) { + return $this->instance; } - [$isEnabled, $supported, $default] = self::loadLangConfig(); + [$isEnabled, $supported, $default] = $this->loadLangConfig(); - $lang = self::detectLanguage($supported, $default); + $lang = $this->detectLanguage($supported, $default); $translator = new Translator($lang); - return self::$instance = new Lang($lang, $isEnabled, $translator); - + return $this->instance = new Lang($lang, $isEnabled, $translator); } /** @@ -64,7 +72,7 @@ public static function get(): Lang * @throws DiException * @throws ReflectionException */ - private static function loadLangConfig(): array + private function loadLangConfig(): array { if (!config()->has('lang')) { config()->import(new Setup('config', 'lang')); @@ -81,16 +89,16 @@ private static function loadLangConfig(): array * @param array $supported * @throws LangException */ - private static function detectLanguage(array $supported, ?string $default): string + private function detectLanguage(array $supported, ?string $default): string { - $lang = self::getLangFromQuery($supported); + $lang = $this->getLangFromQuery($supported); if (in_array($lang, [null, '', '0'], true)) { - $lang = self::getLangFromUrlSegment($supported); + $lang = $this->getLangFromUrlSegment($supported); } if (in_array($lang, [null, '', '0'], true)) { - $lang = self::getLangFromHeader($supported); + $lang = $this->getLangFromHeader($supported); } if (in_array($lang, [null, '', '0'], true)) { @@ -107,7 +115,7 @@ private static function detectLanguage(array $supported, ?string $default): stri /** * @param array $supported */ - private static function getLangFromQuery(array $supported): ?string + private function getLangFromQuery(array $supported): ?string { $queryLang = Request::getQueryParam('lang'); @@ -117,7 +125,7 @@ private static function getLangFromQuery(array $supported): ?string /** * @param array $supported */ - private static function getLangFromUrlSegment(array $supported): ?string + private function getLangFromUrlSegment(array $supported): ?string { $segmentIndex = (int) config()->get('lang.url_segment'); @@ -133,7 +141,7 @@ private static function getLangFromUrlSegment(array $supported): ?string /** * @param array $supported */ - private static function getLangFromHeader(array $supported): ?string + private function getLangFromHeader(array $supported): ?string { $acceptedLang = server()->acceptedLang(); diff --git a/tests/Unit/Lang/Factories/LangFactoryTest.php b/tests/Unit/Lang/Factories/LangFactoryTest.php index 02910fee..8b021fb1 100644 --- a/tests/Unit/Lang/Factories/LangFactoryTest.php +++ b/tests/Unit/Lang/Factories/LangFactoryTest.php @@ -6,6 +6,7 @@ use Quantum\Lang\Factories\LangFactory; use Quantum\Tests\Unit\AppTestCase; use Quantum\Lang\Lang; +use Quantum\Di\Di; class LangFactoryTest extends AppTestCase { @@ -13,7 +14,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(LangFactory::class, 'instance', null); + $this->resetLangFactory(); } public function testLangFactoryGetLangInstance(): void @@ -87,7 +88,7 @@ public function testLangFactoryFallsBackToDefaultIfProvidedLangIsNotSupported(): $this->assertEquals('en', $lang->getLang()); - $this->setPrivateProperty(LangFactory::class, 'instance', null); + $this->resetLangFactory(); $this->testRequest('http://127.0.0.1/api/rest?lang=fr'); @@ -95,7 +96,7 @@ public function testLangFactoryFallsBackToDefaultIfProvidedLangIsNotSupported(): $this->assertEquals('en', $lang->getLang()); - $this->setPrivateProperty(LangFactory::class, 'instance', null); + $this->resetLangFactory(); $this->testRequest('http://127.0.0.1/api/rest', 'GET', [], ['Accept-Language' => 'fr, en;q=0.8, fr;q=0.6']); @@ -121,4 +122,10 @@ public function testLangFactoryThrowsErrorIfNoDefaultLangFound(): void LangFactory::get(); } + + private function resetLangFactory(): void + { + $factory = Di::get(LangFactory::class); + $this->setPrivateProperty($factory, 'instance', null); + } } From f064b91ee3e9cd6c656956ba3b963b1e10097adc Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:42:50 +0400 Subject: [PATCH 17/77] [#381] Migrate ViewFactory from static singleton to DI-managed instance Made-with: Cursor --- src/View/Factories/ViewFactory.php | 25 +++++++++++++------ tests/Unit/View/Factories/ViewFactoryTest.php | 21 +++++++++++++--- tests/Unit/View/QtViewTest.php | 3 ++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index 675d9a35..ec26da6f 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -17,15 +17,16 @@ namespace Quantum\View\Factories; use Quantum\Renderer\Factories\RendererFactory; -use Quantum\ResourceCache\ViewCache; use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; +use Quantum\ResourceCache\ViewCache; use DebugBar\DebugBarException; use Quantum\Asset\AssetManager; use Quantum\Debugger\Debugger; use Quantum\View\QtView; use ReflectionException; +use Quantum\Di\Di; /** * Class ViewFactory @@ -34,23 +35,31 @@ */ class ViewFactory { + private ?QtView $instance = null; + /** - * Instance of QtView + * @throws DebugBarException + * @throws DiException + * @throws BaseException + * @throws ConfigException + * @throws ReflectionException */ - private static ?QtView $instance = null; + public static function get(): QtView + { + return Di::get(self::class)->resolve(); + } /** - * QtView instance * @throws DebugBarException * @throws DiException * @throws BaseException * @throws ConfigException * @throws ReflectionException */ - public static function get(): QtView + public function resolve(): QtView { - if (self::$instance === null) { - self::$instance = new QtView( + if ($this->instance === null) { + $this->instance = new QtView( RendererFactory::get(), AssetManager::getInstance(), Debugger::getInstance(), @@ -58,6 +67,6 @@ public static function get(): QtView ); } - return self::$instance; + return $this->instance; } } diff --git a/tests/Unit/View/Factories/ViewFactoryTest.php b/tests/Unit/View/Factories/ViewFactoryTest.php index e87edf72..e392286d 100644 --- a/tests/Unit/View/Factories/ViewFactoryTest.php +++ b/tests/Unit/View/Factories/ViewFactoryTest.php @@ -5,6 +5,7 @@ use Quantum\View\Factories\ViewFactory; use Quantum\Tests\Unit\AppTestCase; use Quantum\View\QtView; +use Quantum\Di\Di; class ViewFactoryTest extends AppTestCase { @@ -14,21 +15,35 @@ public function setUp(): void { parent::setUp(); - $this->viewFactory = new ViewFactory(); + $this->resetViewFactory(); + $this->viewFactory = Di::get(ViewFactory::class); } public function testGetInstance(): void { - $this->assertInstanceOf(QtView::class, $this->viewFactory->get()); + $this->assertInstanceOf(QtView::class, ViewFactory::get()); + } + + public function testResolveReturnsSameInstance(): void + { + $view1 = $this->viewFactory->resolve(); + $view2 = $this->viewFactory->resolve(); + + $this->assertSame($view1, $view2); } public function testProxyCalls(): void { - $view = $this->viewFactory->get(); + $view = ViewFactory::get(); $view->setParam('key', 'Value'); $this->assertEquals('Value', $view->getParam('key')); } + private function resetViewFactory(): void + { + $factory = Di::get(ViewFactory::class); + $this->setPrivateProperty($factory, 'instance', null); + } } diff --git a/tests/Unit/View/QtViewTest.php b/tests/Unit/View/QtViewTest.php index d1d181f5..ebe76c6e 100644 --- a/tests/Unit/View/QtViewTest.php +++ b/tests/Unit/View/QtViewTest.php @@ -171,7 +171,8 @@ public function testRenderPartial(): void public function testRenderViewWithTwig(): void { - $this->setPrivateProperty(ViewFactory::class, 'instance', null); + $factory = Di::get(ViewFactory::class); + $this->setPrivateProperty($factory, 'instance', null); config()->set('view.default', 'twig'); config()->set('view.twig', ['autoescape' => false]); From c20c9fd12d27bd4c9e65a2e16d757c8cdcdcfff5 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:53:43 +0400 Subject: [PATCH 18/77] [#381] Migrate LoggerFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Logger/Factories/LoggerFactory.php | 29 ++++++++++++------- .../Logger/Factories/LoggerFactoryTest.php | 9 +++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Logger/Factories/LoggerFactory.php b/src/Logger/Factories/LoggerFactory.php index 1feaf289..f7527495 100644 --- a/src/Logger/Factories/LoggerFactory.php +++ b/src/Logger/Factories/LoggerFactory.php @@ -29,6 +29,7 @@ use Quantum\Logger\Logger; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Class LoggerFactory @@ -36,9 +37,6 @@ */ class LoggerFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ LoggerType::SINGLE => SingleAdapter::class, LoggerType::DAILY => DailyAdapter::class, @@ -48,7 +46,7 @@ class LoggerFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -57,6 +55,17 @@ class LoggerFactory * @throws ReflectionException */ public static function get(?string $adapter = null): Logger + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + */ + public function resolve(?string $adapter = null): Logger { if (!config()->has('logging')) { config()->import(new Setup('config', 'logging')); @@ -70,20 +79,20 @@ public static function get(?string $adapter = null): Logger $adapter = $isDebug ? LoggerType::MESSAGE : ($adapter ?? config()->get('logging.default')); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); $logLevel = config()->get('logging.' . $adapter . '.level', 'error'); LoggerConfig::setAppLogLevel($logLevel); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } - private static function createInstance(string $adapterClass, string $adapter): Logger + private function createInstance(string $adapterClass, string $adapter): Logger { if ($adapter === LoggerType::MESSAGE) { return new Logger(new MessageAdapter()); @@ -101,7 +110,7 @@ private static function createInstance(string $adapterClass, string $adapter): L /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw LoggerException::adapterNotSupported($adapter); diff --git a/tests/Unit/Logger/Factories/LoggerFactoryTest.php b/tests/Unit/Logger/Factories/LoggerFactoryTest.php index edb77acd..43ae5204 100644 --- a/tests/Unit/Logger/Factories/LoggerFactoryTest.php +++ b/tests/Unit/Logger/Factories/LoggerFactoryTest.php @@ -11,6 +11,7 @@ use Quantum\Logger\Enums\LoggerType; use Quantum\Tests\Unit\AppTestCase; use Quantum\Logger\Logger; +use Quantum\Di\Di; class LoggerFactoryTest extends AppTestCase { @@ -20,7 +21,7 @@ public function setUp(): void config()->set('app.debug', false); - $this->setPrivateProperty(LoggerFactory::class, 'instances', []); + $this->resetLoggerFactory(); } public function testLoggerFactoryInstance(): void @@ -93,4 +94,10 @@ public function testLoggerFactoryReturnsSameInstance(): void $this->assertSame($logger1, $logger2); } + + private function resetLoggerFactory(): void + { + $factory = Di::get(LoggerFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From 1692dc2099c0f935133277c56c94844494797154 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:57:27 +0400 Subject: [PATCH 19/77] [#381] Migrate SessionFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Session/Factories/SessionFactory.php | 32 +++++++++++++------ .../Session/Factories/SessionFactoryTest.php | 9 +++++- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Session/Factories/SessionFactory.php b/src/Session/Factories/SessionFactory.php index 8a104cf8..599b2579 100644 --- a/src/Session/Factories/SessionFactory.php +++ b/src/Session/Factories/SessionFactory.php @@ -27,6 +27,7 @@ use Quantum\Session\Session; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Class SessionFactory @@ -34,9 +35,6 @@ */ class SessionFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ SessionType::NATIVE => NativeSessionAdapter::class, SessionType::DATABASE => DatabaseSessionAdapter::class, @@ -45,7 +43,7 @@ class SessionFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -54,6 +52,17 @@ class SessionFactory * @throws ReflectionException */ public static function get(?string $adapter = null): Session + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + */ + public function resolve(?string $adapter = null): Session { if (!config()->has('session')) { config()->import(new Setup('config', 'session')); @@ -61,16 +70,19 @@ public static function get(?string $adapter = null): Session $adapter ??= config()->get('session.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } - private static function createInstance(string $adapterClass, string $adapter): Session + /** + * @throws SessionException + */ + private function createInstance(string $adapterClass, string $adapter): Session { $adapterInstance = new $adapterClass(config()->get('session.' . $adapter)); @@ -84,7 +96,7 @@ private static function createInstance(string $adapterClass, string $adapter): S /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw SessionException::adapterNotSupported($adapter); diff --git a/tests/Unit/Session/Factories/SessionFactoryTest.php b/tests/Unit/Session/Factories/SessionFactoryTest.php index 79a1bb01..f51428e5 100644 --- a/tests/Unit/Session/Factories/SessionFactoryTest.php +++ b/tests/Unit/Session/Factories/SessionFactoryTest.php @@ -13,6 +13,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Session\Session; use Quantum\Loader\Setup; +use Quantum\Di\Di; class SessionFactoryTest extends AppTestCase { @@ -22,7 +23,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(SessionFactory::class, 'instances', []); + $this->resetSessionFactory(); IdiormDbal::connect(['driver' => 'sqlite', 'database' => ':memory:']); @@ -119,4 +120,10 @@ public function testSessionFactoryReturnsSameInstance(): void $this->assertSame($session1, $session2); } + + private function resetSessionFactory(): void + { + $factory = Di::get(SessionFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From f101a3cad4ba558b800f2c8e54c7317109cb2102 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:00:33 +0400 Subject: [PATCH 20/77] [#381] Migrate CacheFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Cache/Factories/CacheFactory.php | 29 ++++++++++++------- .../Unit/Cache/Factories/CacheFactoryTest.php | 9 +++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Cache/Factories/CacheFactory.php b/src/Cache/Factories/CacheFactory.php index 7f2cc130..aeff67f2 100644 --- a/src/Cache/Factories/CacheFactory.php +++ b/src/Cache/Factories/CacheFactory.php @@ -29,6 +29,7 @@ use Quantum\Loader\Setup; use ReflectionException; use Quantum\Cache\Cache; +use Quantum\Di\Di; /** * Class CacheFactory @@ -36,9 +37,6 @@ */ class CacheFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ CacheType::FILE => FileAdapter::class, CacheType::DATABASE => DatabaseAdapter::class, @@ -49,7 +47,7 @@ class CacheFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -58,6 +56,17 @@ class CacheFactory * @throws ReflectionException */ public static function get(?string $adapter = null): Cache + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + */ + public function resolve(?string $adapter = null): Cache { if (!config()->has('cache')) { config()->import(new Setup('config', 'cache')); @@ -65,19 +74,19 @@ public static function get(?string $adapter = null): Cache $adapter ??= config()->get('cache.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } /** * @throws CacheException */ - private static function createInstance(string $adapterClass, string $adapter): Cache + private function createInstance(string $adapterClass, string $adapter): Cache { $cacheAdapter = new $adapterClass(config()->get('cache.' . $adapter)); @@ -91,7 +100,7 @@ private static function createInstance(string $adapterClass, string $adapter): C /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw CacheException::adapterNotSupported($adapter); diff --git a/tests/Unit/Cache/Factories/CacheFactoryTest.php b/tests/Unit/Cache/Factories/CacheFactoryTest.php index 7bed8d83..10b674e7 100644 --- a/tests/Unit/Cache/Factories/CacheFactoryTest.php +++ b/tests/Unit/Cache/Factories/CacheFactoryTest.php @@ -11,6 +11,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Cache\Enums\CacheType; use Quantum\Cache\Cache; +use Quantum\Di\Di; class CacheFactoryTest extends AppTestCase { @@ -18,7 +19,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(CacheFactory::class, 'instances', []); + $this->resetCacheFactory(); } public function testCacheFactoryInstance(): void @@ -79,4 +80,10 @@ public function testCacheFactoryReturnsSameInstance(): void $this->assertSame($cache1, $cache2); } + + private function resetCacheFactory(): void + { + $factory = Di::get(CacheFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From 42549070ab78142f6e6de86cd6f60846c200a1d9 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:03:45 +0400 Subject: [PATCH 21/77] [#381] Migrate FileSystemFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Storage/Factories/FileSystemFactory.php | 43 +++++++++++---------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Storage/Factories/FileSystemFactory.php b/src/Storage/Factories/FileSystemFactory.php index aff8bcbc..9e652d54 100644 --- a/src/Storage/Factories/FileSystemFactory.php +++ b/src/Storage/Factories/FileSystemFactory.php @@ -35,6 +35,7 @@ use Quantum\Storage\FileSystem; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Class FileSystemFactory @@ -42,18 +43,12 @@ */ class FileSystemFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ FileSystemType::LOCAL => LocalFileSystemAdapter::class, FileSystemType::DROPBOX => DropboxFileSystemAdapter::class, FileSystemType::GDRIVE => GoogleDriveFileSystemAdapter::class, ]; - /** - * Supported apps - */ public const APPS = [ FileSystemType::DROPBOX => DropboxApp::class, FileSystemType::GDRIVE => GoogleDriveApp::class, @@ -62,7 +57,7 @@ class FileSystemFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -71,6 +66,17 @@ class FileSystemFactory * @throws ConfigException */ public static function get(?string $adapter = null): FileSystem + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws DiException + * @throws ReflectionException + * @throws ConfigException + */ + public function resolve(?string $adapter = null): FileSystem { if (!config()->has('fs')) { config()->import(new Setup('config', 'fs')); @@ -78,13 +84,13 @@ public static function get(?string $adapter = null): FileSystem $adapter ??= config()->get('fs.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } /** @@ -93,9 +99,9 @@ public static function get(?string $adapter = null): FileSystem * @throws ReflectionException * @throws ServiceException */ - private static function createInstance(string $adapterClass, string $adapter): FileSystem + private function createInstance(string $adapterClass, string $adapter): FileSystem { - $fsAdapter = new $adapterClass(self::createCloudApp($adapter)); + $fsAdapter = new $adapterClass($this->createCloudApp($adapter)); if (!$fsAdapter instanceof FilesystemAdapterInterface) { throw FileSystemException::adapterNotSupported($adapter); @@ -107,7 +113,7 @@ private static function createInstance(string $adapterClass, string $adapter): F /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw FileSystemException::adapterNotSupported($adapter); @@ -122,7 +128,7 @@ private static function getAdapterClass(string $adapter): string * @throws ReflectionException * @throws ServiceException */ - private static function createCloudApp(string $adapter): ?CloudAppInterface + private function createCloudApp(string $adapter): ?CloudAppInterface { if ($adapter === FileSystemType::LOCAL || !isset(self::APPS[$adapter])) { return null; @@ -133,21 +139,18 @@ private static function createCloudApp(string $adapter): ?CloudAppInterface return new $cloudAppClass( config()->get('fs.' . $adapter . '.params.app_key'), config()->get('fs.' . $adapter . '.params.app_secret'), - self::createTokenService($adapter), + $this->createTokenService($adapter), new HttpClient() ); } /** - * Creates token service instance - * @param string $adapter - * @return TokenServiceInterface * @throws BaseException * @throws DiException * @throws ReflectionException * @throws ServiceException */ - private static function createTokenService(string $adapter): TokenServiceInterface + private function createTokenService(string $adapter): TokenServiceInterface { $serviceClass = (string) config()->get('fs.' . $adapter . '.service'); From a91afc341afaa4c560ca48701d0728bc2f4fdd76 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:07:01 +0400 Subject: [PATCH 22/77] [#381] Migrate AuthFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Auth/Factories/AuthFactory.php | 39 ++++++++++++------- tests/Unit/Auth/Factories/AuthFactoryTest.php | 9 ++++- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/Auth/Factories/AuthFactory.php b/src/Auth/Factories/AuthFactory.php index bf2c9b12..a1f9f663 100644 --- a/src/Auth/Factories/AuthFactory.php +++ b/src/Auth/Factories/AuthFactory.php @@ -33,6 +33,7 @@ use Quantum\Loader\Setup; use ReflectionException; use Quantum\Auth\Auth; +use Quantum\Di\Di; /** * Class AuthFactory @@ -40,9 +41,6 @@ */ class AuthFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ AuthType::SESSION => SessionAuthAdapter::class, AuthType::JWT => JwtAuthAdapter::class, @@ -51,7 +49,7 @@ class AuthFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws AuthException @@ -62,6 +60,19 @@ class AuthFactory * @throws ServiceException */ public static function get(?string $adapter = null): Auth + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws AuthException + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + * @throws ServiceException + */ + public function resolve(?string $adapter = null): Auth { if (!config()->has('auth')) { config()->import(new Setup('config', 'auth')); @@ -69,13 +80,13 @@ public static function get(?string $adapter = null): Auth $adapter ??= config()->get('auth.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } /** @@ -86,13 +97,13 @@ public static function get(?string $adapter = null): Auth * @throws ReflectionException * @throws ServiceException */ - private static function createInstance(string $adapterClass, string $adapter): Auth + private function createInstance(string $adapterClass, string $adapter): Auth { - $authService = self::createAuthService($adapter); + $authService = $this->createAuthService($adapter); $authConfig = (array) config()->get('auth'); $adapterInstance = $adapter === AuthType::JWT - ? new $adapterClass($authService, mailer(), new Hasher(), self::createJwtInstance(), $authConfig) + ? new $adapterClass($authService, mailer(), new Hasher(), $this->createJwtInstance(), $authConfig) : new $adapterClass($authService, mailer(), new Hasher(), $authConfig); if (!$adapterInstance instanceof AuthenticatableInterface) { @@ -105,7 +116,7 @@ private static function createInstance(string $adapterClass, string $adapter): A /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw AuthException::adapterNotSupported($adapter); @@ -120,7 +131,7 @@ private static function getAdapterClass(string $adapter): string * @throws ReflectionException * @throws ServiceException */ - private static function createAuthService(string $adapter): AuthServiceInterface + private function createAuthService(string $adapter): AuthServiceInterface { $authServiceClass = config()->get('auth.' . $adapter . '.service'); @@ -134,7 +145,7 @@ private static function createAuthService(string $adapter): AuthServiceInterface return $authService; } - private static function createJwtInstance(): JwtToken + private function createJwtInstance(): JwtToken { return (new JwtToken()) ->setLeeway(1) diff --git a/tests/Unit/Auth/Factories/AuthFactoryTest.php b/tests/Unit/Auth/Factories/AuthFactoryTest.php index 37a3521a..40cebb18 100644 --- a/tests/Unit/Auth/Factories/AuthFactoryTest.php +++ b/tests/Unit/Auth/Factories/AuthFactoryTest.php @@ -9,6 +9,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Auth\Enums\AuthType; use Quantum\Auth\Auth; +use Quantum\Di\Di; class AuthFactoryTest extends AppTestCase { @@ -16,7 +17,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(AuthFactory::class, 'instances', []); + $this->resetAuthFactory(); } public function testAuthFactoryInstance(): void @@ -65,4 +66,10 @@ public function testAuthFactoryReturnsSameInstance(): void $this->assertSame($auth1, $auth2); } + + private function resetAuthFactory(): void + { + $factory = Di::get(AuthFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From e711bb121317844f2feaed12510ed559f2d9adf6 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:09:16 +0400 Subject: [PATCH 23/77] [#381] Migrate RendererFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Renderer/Factories/RendererFactory.php | 29 ++++++++++++------- .../Factories/RendererFactoryTest.php | 9 +++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Renderer/Factories/RendererFactory.php b/src/Renderer/Factories/RendererFactory.php index 4112f888..5129ba50 100644 --- a/src/Renderer/Factories/RendererFactory.php +++ b/src/Renderer/Factories/RendererFactory.php @@ -27,6 +27,7 @@ use Quantum\Renderer\Renderer; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Class RendererFactory @@ -34,9 +35,6 @@ */ class RendererFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ RendererType::HTML => HtmlAdapter::class, RendererType::TWIG => TwigAdapter::class, @@ -45,7 +43,7 @@ class RendererFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -54,6 +52,17 @@ class RendererFactory * @throws ReflectionException */ public static function get(?string $adapter = null): Renderer + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + */ + public function resolve(?string $adapter = null): Renderer { if (!config()->has('view')) { config()->import(new Setup('config', 'view')); @@ -61,19 +70,19 @@ public static function get(?string $adapter = null): Renderer $adapter ??= config()->get('view.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } /** * @throws RendererException */ - private static function createInstance(string $adapterClass, string $adapter): Renderer + private function createInstance(string $adapterClass, string $adapter): Renderer { $adapterInstance = new $adapterClass(config()->get('view.' . $adapter)); @@ -87,7 +96,7 @@ private static function createInstance(string $adapterClass, string $adapter): R /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw RendererException::adapterNotSupported($adapter); diff --git a/tests/Unit/Renderer/Factories/RendererFactoryTest.php b/tests/Unit/Renderer/Factories/RendererFactoryTest.php index e3b2858e..deb9f599 100644 --- a/tests/Unit/Renderer/Factories/RendererFactoryTest.php +++ b/tests/Unit/Renderer/Factories/RendererFactoryTest.php @@ -10,6 +10,7 @@ use Quantum\Renderer\Enums\RendererType; use Quantum\Tests\Unit\AppTestCase; use Quantum\Renderer\Renderer; +use Quantum\Di\Di; class RendererFactoryTest extends AppTestCase { @@ -17,7 +18,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(RendererFactory::class, 'instances', []); + $this->resetRendererFactory(); } public function testRendererFactoryInstance(): void @@ -70,4 +71,10 @@ public function testRendererFactoryReturnsSameInstance(): void $this->assertSame($renderer1, $renderer2); } + + private function resetRendererFactory(): void + { + $factory = Di::get(RendererFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From 12ef4ffe9a83896f051e84e2fed9bfcb02102ade Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:11:25 +0400 Subject: [PATCH 24/77] [#381] Migrate MailerFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Mailer/Factories/MailerFactory.php | 29 ++++++++++++------- .../Mailer/Factories/MailerFactoryTest.php | 9 +++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Mailer/Factories/MailerFactory.php b/src/Mailer/Factories/MailerFactory.php index 65a2d293..fdae16d0 100644 --- a/src/Mailer/Factories/MailerFactory.php +++ b/src/Mailer/Factories/MailerFactory.php @@ -31,6 +31,7 @@ use Quantum\Mailer\Mailer; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * class MailerFactory @@ -38,9 +39,6 @@ */ class MailerFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ MailerType::SMTP => SmtpAdapter::class, MailerType::MAILGUN => MailgunAdapter::class, @@ -53,7 +51,7 @@ class MailerFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -62,6 +60,17 @@ class MailerFactory * @throws ReflectionException */ public static function get(?string $adapter = null): Mailer + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + */ + public function resolve(?string $adapter = null): Mailer { if (!config()->has('mailer')) { config()->import(new Setup('config', 'mailer')); @@ -69,19 +78,19 @@ public static function get(?string $adapter = null): Mailer $adapter ??= config()->get('mailer.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } /** * @throws MailerException */ - private static function createInstance(string $adapterClass, string $adapter): Mailer + private function createInstance(string $adapterClass, string $adapter): Mailer { $adapterInstance = new $adapterClass(config()->get('mailer.' . $adapter)); @@ -95,7 +104,7 @@ private static function createInstance(string $adapterClass, string $adapter): M /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw MailerException::adapterNotSupported($adapter); diff --git a/tests/Unit/Mailer/Factories/MailerFactoryTest.php b/tests/Unit/Mailer/Factories/MailerFactoryTest.php index 9ad9111e..152e6516 100644 --- a/tests/Unit/Mailer/Factories/MailerFactoryTest.php +++ b/tests/Unit/Mailer/Factories/MailerFactoryTest.php @@ -14,6 +14,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Mailer\Mailer; use Quantum\Loader\Setup; +use Quantum\Di\Di; class MailerFactoryTest extends AppTestCase { @@ -21,7 +22,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(MailerFactory::class, 'instances', []); + $this->resetMailerFactory(); if (!config()->has('mailer')) { config()->import(new Setup('config', 'mailer')); @@ -100,4 +101,10 @@ public function testMailerFactoryReturnsSameInstance(): void $this->assertSame($mailer1, $mailer2); } + + private function resetMailerFactory(): void + { + $factory = Di::get(MailerFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From cd93ad6f089b28e40222b56bd56290d798c41d8e Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:13:43 +0400 Subject: [PATCH 25/77] [#381] Migrate CaptchaFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Captcha/Factories/CaptchaFactory.php | 29 ++++++++++++------- .../Captcha/Factories/CaptchaFactoryTest.php | 9 +++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Captcha/Factories/CaptchaFactory.php b/src/Captcha/Factories/CaptchaFactory.php index 751e4613..820c3e56 100644 --- a/src/Captcha/Factories/CaptchaFactory.php +++ b/src/Captcha/Factories/CaptchaFactory.php @@ -28,6 +28,7 @@ use Quantum\Captcha\Captcha; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Class CaptchaFactory @@ -35,9 +36,6 @@ */ class CaptchaFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ CaptchaType::HCAPTCHA => HcaptchaAdapter::class, CaptchaType::RECAPTCHA => RecaptchaAdapter::class, @@ -46,7 +44,7 @@ class CaptchaFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException @@ -55,6 +53,17 @@ class CaptchaFactory * @throws ReflectionException */ public static function get(?string $adapter = null): Captcha + { + return Di::get(self::class)->resolve($adapter); + } + + /** + * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException + */ + public function resolve(?string $adapter = null): Captcha { if (!config()->has('captcha')) { config()->import(new Setup('config', 'captcha')); @@ -62,19 +71,19 @@ public static function get(?string $adapter = null): Captcha $adapter ??= config()->get('captcha.default'); - $adapterClass = self::getAdapterClass($adapter); + $adapterClass = $this->getAdapterClass($adapter); - if (!isset(self::$instances[$adapter])) { - self::$instances[$adapter] = self::createInstance($adapterClass, $adapter); + if (!isset($this->instances[$adapter])) { + $this->instances[$adapter] = $this->createInstance($adapterClass, $adapter); } - return self::$instances[$adapter]; + return $this->instances[$adapter]; } /** * @throws CaptchaException */ - private static function createInstance(string $adapterClass, string $adapter): Captcha + private function createInstance(string $adapterClass, string $adapter): Captcha { $adapterInstance = new $adapterClass(config()->get('captcha.' . $adapter), new HttpClient()); @@ -88,7 +97,7 @@ private static function createInstance(string $adapterClass, string $adapter): C /** * @throws BaseException */ - private static function getAdapterClass(string $adapter): string + private function getAdapterClass(string $adapter): string { if (!array_key_exists($adapter, self::ADAPTERS)) { throw CaptchaException::adapterNotSupported($adapter); diff --git a/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php b/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php index 798f681f..24aa28c8 100644 --- a/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php +++ b/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php @@ -9,6 +9,7 @@ use Quantum\Captcha\Enums\CaptchaType; use Quantum\Tests\Unit\AppTestCase; use Quantum\Captcha\Captcha; +use Quantum\Di\Di; class CaptchaFactoryTest extends AppTestCase { @@ -16,7 +17,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(CaptchaFactory::class, 'instances', []); + $this->resetCaptchaFactory(); } public function testCaptchaFactoryInstance(): void @@ -63,4 +64,10 @@ public function testAuthFactoryReturnsSameInstance(): void $this->assertSame($captcha1, $captcha2); } + + private function resetCaptchaFactory(): void + { + $factory = Di::get(CaptchaFactory::class); + $this->setPrivateProperty($factory, 'instances', []); + } } From a42e94884643c1996f561f52af33d10273075cd1 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:16:27 +0400 Subject: [PATCH 26/77] [#381] Migrate CryptorFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Encryption/Factories/CryptorFactory.php | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Encryption/Factories/CryptorFactory.php b/src/Encryption/Factories/CryptorFactory.php index a9129314..e9dcb5fb 100644 --- a/src/Encryption/Factories/CryptorFactory.php +++ b/src/Encryption/Factories/CryptorFactory.php @@ -22,6 +22,7 @@ use Quantum\App\Exceptions\BaseException; use Quantum\Encryption\Enums\CryptorType; use Quantum\Encryption\Cryptor; +use Quantum\Di\Di; /** * Class Cryptor @@ -29,9 +30,6 @@ */ class CryptorFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ CryptorType::SYMMETRIC => SymmetricEncryptionAdapter::class, CryptorType::ASYMMETRIC => AsymmetricEncryptionAdapter::class, @@ -40,24 +38,32 @@ class CryptorFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException */ public static function get(string $type = CryptorType::SYMMETRIC): Cryptor { - if (!isset(self::$instances[$type])) { - self::$instances[$type] = self::createInstance($type); + return Di::get(self::class)->resolve($type); + } + + /** + * @throws BaseException + */ + public function resolve(string $type = CryptorType::SYMMETRIC): Cryptor + { + if (!isset($this->instances[$type])) { + $this->instances[$type] = $this->createInstance($type); } - return self::$instances[$type]; + return $this->instances[$type]; } /** * @throws BaseException */ - private static function createInstance(string $type): Cryptor + private function createInstance(string $type): Cryptor { if (!isset(self::ADAPTERS[$type])) { throw CryptorException::adapterNotSupported($type); From 4edcf9ce05f6f24935f69844d8d78b5978b43174 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:28:05 +0400 Subject: [PATCH 27/77] [#381] Migrate ArchiveFactory from static instances cache to DI-managed instance Made-with: Cursor --- src/Archive/Factories/ArchiveFactory.php | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Archive/Factories/ArchiveFactory.php b/src/Archive/Factories/ArchiveFactory.php index 4b07dbab..da8c7eb7 100644 --- a/src/Archive/Factories/ArchiveFactory.php +++ b/src/Archive/Factories/ArchiveFactory.php @@ -22,16 +22,14 @@ use Quantum\Archive\Adapters\ZipAdapter; use Quantum\Archive\Enums\ArchiveType; use Quantum\Archive\Archive; +use Quantum\Di\Di; /** - * Class Cryptor - * @package Quantum\Encryption + * Class ArchiveFactory + * @package Quantum\Archive */ class ArchiveFactory { - /** - * Supported adapters - */ public const ADAPTERS = [ ArchiveType::PHAR => PharAdapter::class, ArchiveType::ZIP => ZipAdapter::class, @@ -40,24 +38,32 @@ class ArchiveFactory /** * @var array */ - private static array $instances = []; + private array $instances = []; /** * @throws BaseException */ public static function get(string $type = ArchiveType::PHAR): Archive { - if (!isset(self::$instances[$type])) { - self::$instances[$type] = self::createInstance($type); + return Di::get(self::class)->resolve($type); + } + + /** + * @throws BaseException + */ + public function resolve(string $type = ArchiveType::PHAR): Archive + { + if (!isset($this->instances[$type])) { + $this->instances[$type] = $this->createInstance($type); } - return self::$instances[$type]; + return $this->instances[$type]; } /** * @throws BaseException */ - private static function createInstance(string $type): Archive + private function createInstance(string $type): Archive { if (!isset(self::ADAPTERS[$type])) { throw ArchiveException::adapterNotSupported($type); From 1cb59fbd0d54ae231c84d6812d00c67841f232ef Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:34:25 +0400 Subject: [PATCH 28/77] [#381] Remove redundant core dependencies.php and RegisterCoreDependenciesStage Made-with: Cursor --- src/App/Adapters/ConsoleAppAdapter.php | 2 - src/App/Adapters/WebAppAdapter.php | 2 - src/App/Config/dependencies.php | 8 ---- .../Stages/RegisterCoreDependenciesStage.php | 37 ------------------ .../App/Stages/LoadAppConfigStageTest.php | 2 - .../App/Stages/LoadEnvironmentStageTest.php | 2 - .../Unit/App/Stages/LoadHelpersStageTest.php | 4 -- .../Unit/App/Stages/LoadLanguageStageTest.php | 2 - .../RegisterCoreDependenciesStageTest.php | 39 ------------------- .../App/Stages/SetupErrorHandlerStageTest.php | 2 - 10 files changed, 100 deletions(-) delete mode 100644 src/App/Config/dependencies.php delete mode 100644 src/App/Stages/RegisterCoreDependenciesStage.php delete mode 100644 tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index c9692a87..3ad4d469 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -16,7 +16,6 @@ namespace Quantum\App\Adapters; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Symfony\Component\Console\Output\ConsoleOutput; use Quantum\App\Exceptions\StopExecutionException; use Symfony\Component\Console\Input\ArgvInput; @@ -59,7 +58,6 @@ public function __construct() $commandName = $this->input->getFirstArgument(); $stages = [ - new RegisterCoreDependenciesStage(), new LoadHelpersStage(), ]; diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index e1267c4b..9d88bf0c 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -17,7 +17,6 @@ namespace Quantum\App\Adapters; use Quantum\Middleware\Exceptions\MiddlewareException; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\Database\Exceptions\DatabaseException; use Quantum\App\Exceptions\StopExecutionException; use Quantum\Session\Exceptions\SessionException; @@ -79,7 +78,6 @@ public function __construct() parent::__construct(AppType::WEB); $pipeline = new BootPipeline([ - new RegisterCoreDependenciesStage(), new LoadHelpersStage(), new LoadEnvironmentStage(), new LoadAppConfigStage(), diff --git a/src/App/Config/dependencies.php b/src/App/Config/dependencies.php deleted file mode 100644 index 918a0af4..00000000 --- a/src/App/Config/dependencies.php +++ /dev/null @@ -1,8 +0,0 @@ - \Quantum\Loader\Loader::class, - \Quantum\Http\Request::class => \Quantum\Http\Request::class, - \Quantum\Http\Response::class => \Quantum\Http\Response::class, - \Quantum\Config\Config::class => \Quantum\Config\Config::class, -]; diff --git a/src/App/Stages/RegisterCoreDependenciesStage.php b/src/App/Stages/RegisterCoreDependenciesStage.php deleted file mode 100644 index fcb90233..00000000 --- a/src/App/Stages/RegisterCoreDependenciesStage.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) - * @link http://quantum.softberg.org/ - * @since 3.0.0 - */ - -namespace Quantum\App\Stages; - -use Quantum\App\Contracts\BootStageInterface; -use Quantum\App\AppContext; -use Quantum\Di\Di; - -/** - * Class RegisterCoreDependenciesStage - * @package Quantum\App - */ -class RegisterCoreDependenciesStage implements BootStageInterface -{ - public function process(AppContext $context): void - { - $file = dirname(__DIR__) . DS . 'Config' . DS . 'dependencies.php'; - - $coreDependencies = (is_file($file) && is_array($deps = require $file)) ? $deps : []; - - Di::registerDependencies($coreDependencies); - } -} diff --git a/tests/Unit/App/Stages/LoadAppConfigStageTest.php b/tests/Unit/App/Stages/LoadAppConfigStageTest.php index 259fc84b..9c18ff80 100644 --- a/tests/Unit/App/Stages/LoadAppConfigStageTest.php +++ b/tests/Unit/App/Stages/LoadAppConfigStageTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\App\Stages; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; @@ -21,7 +20,6 @@ public function setUp(): void $context = new AppContext(AppType::WEB); - (new RegisterCoreDependenciesStage())->process($context); (new LoadHelpersStage())->process($context); (new LoadEnvironmentStage())->process($context); } diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php index 0f9b0578..6e291d17 100644 --- a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\App\Stages; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadHelpersStage; use PHPUnit\Framework\TestCase; @@ -20,7 +19,6 @@ public function setUp(): void $context = new AppContext(AppType::WEB); - (new RegisterCoreDependenciesStage())->process($context); (new LoadHelpersStage())->process($context); } diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php index 730b52ed..8a2a1dfa 100644 --- a/tests/Unit/App/Stages/LoadHelpersStageTest.php +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\App\Stages; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Enums\AppType; use Quantum\App\AppContext; @@ -16,9 +15,6 @@ public function setUp(): void { Di::reset(); App::setBaseDir(PROJECT_ROOT); - - $depsStage = new RegisterCoreDependenciesStage(); - $depsStage->process(new AppContext(AppType::WEB)); } public function tearDown(): void diff --git a/tests/Unit/App/Stages/LoadLanguageStageTest.php b/tests/Unit/App/Stages/LoadLanguageStageTest.php index 4d4dd233..cfdce4b3 100644 --- a/tests/Unit/App/Stages/LoadLanguageStageTest.php +++ b/tests/Unit/App/Stages/LoadLanguageStageTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\App\Stages; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadLanguageStage; @@ -22,7 +21,6 @@ public function setUp(): void $context = new AppContext(AppType::WEB); - (new RegisterCoreDependenciesStage())->process($context); (new LoadHelpersStage())->process($context); (new LoadEnvironmentStage())->process($context); (new LoadAppConfigStage())->process($context); diff --git a/tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php b/tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php deleted file mode 100644 index 8ad8a683..00000000 --- a/tests/Unit/App/Stages/RegisterCoreDependenciesStageTest.php +++ /dev/null @@ -1,39 +0,0 @@ -assertFalse(Di::isRegistered(Loader::class)); - $this->assertFalse(Di::isRegistered(Request::class)); - $this->assertFalse(Di::isRegistered(Response::class)); - - $stage = new RegisterCoreDependenciesStage(); - $stage->process(new AppContext(AppType::WEB)); - - $this->assertTrue(Di::isRegistered(Loader::class)); - $this->assertTrue(Di::isRegistered(Request::class)); - $this->assertTrue(Di::isRegistered(Response::class)); - } -} diff --git a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php index 14148cc1..844864ac 100644 --- a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php +++ b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\App\Stages; -use Quantum\App\Stages\RegisterCoreDependenciesStage; use Quantum\App\Stages\SetupErrorHandlerStage; use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadAppConfigStage; @@ -22,7 +21,6 @@ public function setUp(): void $context = new AppContext(AppType::WEB); - (new RegisterCoreDependenciesStage())->process($context); (new LoadHelpersStage())->process($context); (new LoadEnvironmentStage())->process($context); (new LoadAppConfigStage())->process($context); From b4bbf540ea129a4afeadaa00ab2b0fc6caea95b6 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:05:59 +0400 Subject: [PATCH 29/77] [#382] Migrate Server singleton to DI ownership Made-with: Cursor --- src/App/Traits/WebAppTrait.php | 3 +- src/Environment/Helpers/server.php | 16 ++------ src/Environment/Server.php | 24 +----------- src/Http/Traits/Request/Internal.php | 5 +-- src/Http/Traits/Request/RawInput.php | 3 +- .../Environment/Helpers/ServerHelperTest.php | 23 ++++-------- tests/Unit/Environment/ServerTest.php | 37 ++++++++++--------- .../Http/Traits/Request/HttpRawInputTest.php | 9 ++--- .../Traits/Request/HttpRequestHeaderTest.php | 11 +++--- .../Request/HttpRequestInternalTest.php | 9 ++--- 10 files changed, 50 insertions(+), 90 deletions(-) diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index d7155d32..e3d58e87 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -23,7 +23,6 @@ use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; use DebugBar\DebugBarException; -use Quantum\Environment\Server; use Quantum\Debugger\Debugger; use Quantum\Http\Response; use Quantum\Http\Request; @@ -44,7 +43,7 @@ trait WebAppTrait */ private function initializeRequestResponse(Request $request, Response $response): void { - $request->init(Server::getInstance()); + $request->init(server()); $response->init(); } diff --git a/src/Environment/Helpers/server.php b/src/Environment/Helpers/server.php index 652fce03..b63b3171 100644 --- a/src/Environment/Helpers/server.php +++ b/src/Environment/Helpers/server.php @@ -13,33 +13,25 @@ */ use Quantum\Environment\Server; +use Quantum\Di\Di; -/** - * Gets Server instance - */ function server(): Server { - return Server::getInstance(); + return Di::get(Server::class); } -/** - * Gets user IP - */ function get_user_ip(): ?string { - return Server::getInstance()->ip(); + return Di::get(Server::class)->ip(); } if (!function_exists('getallheaders')) { /** - * Get all headers - * Built-in PHP function synonym of apache_request_headers() - * Declaring here for Nginx server * @return array */ function getallheaders(): array { - return Server::getInstance()->getAllHeaders(); + return Di::get(Server::class)->getAllHeaders(); } } diff --git a/src/Environment/Server.php b/src/Environment/Server.php index 9cbc50b7..5128cd62 100644 --- a/src/Environment/Server.php +++ b/src/Environment/Server.php @@ -27,28 +27,11 @@ class Server */ private array $server; - private static ?Server $instance = null; - - /** - * Server constructor. - */ - private function __construct() + public function __construct() { $this->server = $_SERVER; } - /** - * Get Instance - */ - public static function getInstance(): Server - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * Flushes the server params */ @@ -74,10 +57,7 @@ public function get(string $key) return $this->server[$key] ?? null; } - /** - * @param string $key - */ - public function has($key): bool + public function has(string $key): bool { return array_key_exists($key, $this->server); } diff --git a/src/Http/Traits/Request/Internal.php b/src/Http/Traits/Request/Internal.php index 92c2e35a..164dd75a 100644 --- a/src/Http/Traits/Request/Internal.php +++ b/src/Http/Traits/Request/Internal.php @@ -50,7 +50,7 @@ public static function create( ): void { $parsed = parse_url($url); - $server = Server::getInstance(); + $server = server(); $server->flush(); @@ -103,11 +103,10 @@ public static function create( /** * Detects the content type - * @param Server $server * @param array|null $data * @param array|null $files */ - protected static function detectAndSetContentType($server, ?array $data = null, ?array $files = null): void + protected static function detectAndSetContentType(Server $server, ?array $data = null, ?array $files = null): void { if ($files && count($files) > 0) { $server->set('CONTENT_TYPE', ContentType::FORM_DATA); diff --git a/src/Http/Traits/Request/RawInput.php b/src/Http/Traits/Request/RawInput.php index de81eff7..caea01e4 100644 --- a/src/Http/Traits/Request/RawInput.php +++ b/src/Http/Traits/Request/RawInput.php @@ -22,7 +22,6 @@ use Quantum\Di\Exceptions\DiException; use Quantum\Http\Enums\ContentType; use Quantum\Storage\UploadedFile; -use Quantum\Environment\Server; use ReflectionException; /** @@ -57,7 +56,7 @@ public static function parse(string $rawInput): array */ private static function getBoundary(): ?string { - $contentType = Server::getInstance()->contentType(); + $contentType = server()->contentType(); if (!$contentType) { return null; diff --git a/tests/Unit/Environment/Helpers/ServerHelperTest.php b/tests/Unit/Environment/Helpers/ServerHelperTest.php index 520b2a0b..7de1f9e6 100644 --- a/tests/Unit/Environment/Helpers/ServerHelperTest.php +++ b/tests/Unit/Environment/Helpers/ServerHelperTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Environment\Helpers; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Environment\Server; class ServerHelperTest extends AppTestCase { @@ -11,42 +10,36 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(Server::class, 'instance', null); + server()->flush(); } public function testGetUserIpFromClientIp(): void { - $_SERVER['HTTP_CLIENT_IP'] = '192.168.1.1'; - $_SERVER['HTTP_X_FORWARDED_FOR'] = null; - $_SERVER['REMOTE_ADDR'] = null; + server()->set('HTTP_CLIENT_IP', '192.168.1.1'); $this->assertEquals('192.168.1.1', get_user_ip()); } public function testGetUserIpFromXForwardedFor(): void { - $_SERVER['HTTP_CLIENT_IP'] = null; - $_SERVER['HTTP_X_FORWARDED_FOR'] = '203.0.113.5'; - $_SERVER['REMOTE_ADDR'] = null; + server()->set('HTTP_X_FORWARDED_FOR', '203.0.113.5'); $this->assertEquals('203.0.113.5', get_user_ip()); } public function testGetUserIpFromRemoteAddr(): void { - $_SERVER['HTTP_CLIENT_IP'] = null; - $_SERVER['HTTP_X_FORWARDED_FOR'] = null; - $_SERVER['REMOTE_ADDR'] = '198.51.100.1'; + server()->set('REMOTE_ADDR', '198.51.100.1'); $this->assertEquals('198.51.100.1', get_user_ip()); } public function testGetAllHeaders(): void { - $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0'; - $_SERVER['HTTP_ACCEPT'] = 'text/html'; - $_SERVER['HTTP_X_CUSTOM_HEADER'] = 'CustomValue'; - $_SERVER['SERVER_NAME'] = 'example.com'; + server()->set('HTTP_USER_AGENT', 'Mozilla/5.0'); + server()->set('HTTP_ACCEPT', 'text/html'); + server()->set('HTTP_X_CUSTOM_HEADER', 'CustomValue'); + server()->set('SERVER_NAME', 'example.com'); $headers = getallheaders(); diff --git a/tests/Unit/Environment/ServerTest.php b/tests/Unit/Environment/ServerTest.php index 336398f0..56d5e55d 100644 --- a/tests/Unit/Environment/ServerTest.php +++ b/tests/Unit/Environment/ServerTest.php @@ -4,6 +4,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Environment\Server; +use Quantum\Di\Di; class ServerTest extends AppTestCase { @@ -11,20 +12,20 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(Server::class, 'instance', null); + server()->flush(); } - public function testServerGetInstance(): void + public function testServerDiReturnsSameInstance(): void { - $server1 = Server::getInstance(); - $server2 = Server::getInstance(); + $server1 = Di::get(Server::class); + $server2 = Di::get(Server::class); $this->assertSame($server1, $server2); } public function testServerAll(): void { - $server = Server::getInstance(); + $server = server(); $server->set('REQUEST_METHOD', 'GET'); $server->set('REQUEST_URI', '/test'); @@ -39,7 +40,7 @@ public function testServerAll(): void public function testServerSetAndGet(): void { - $server = Server::getInstance(); + $server = server(); $server->set('REQUEST_METHOD', 'POST'); @@ -54,7 +55,7 @@ public function testServerSetAndGet(): void public function testServerUri(): void { - $server = Server::getInstance(); + $server = server(); $server->set('REQUEST_URI', '/test/uri'); $this->assertEquals('/test/uri', $server->uri()); @@ -62,7 +63,7 @@ public function testServerUri(): void public function testServerQuery(): void { - $server = Server::getInstance(); + $server = server(); $server->set('QUERY_STRING', 'foo=bar'); $this->assertEquals('foo=bar', $server->query()); @@ -70,7 +71,7 @@ public function testServerQuery(): void public function testServerMethod(): void { - $server = Server::getInstance(); + $server = server(); $server->set('REQUEST_METHOD', 'PUT'); $this->assertEquals('PUT', $server->method()); @@ -78,7 +79,7 @@ public function testServerMethod(): void public function testServerProtocol(): void { - $server = Server::getInstance(); + $server = server(); $server->set('HTTPS', 'on'); $server->set('SERVER_PORT', 443); @@ -92,7 +93,7 @@ public function testServerProtocol(): void public function testServerHost(): void { - $server = Server::getInstance(); + $server = server(); $server->set('SERVER_NAME', 'localhost'); $this->assertEquals('localhost', $server->host()); @@ -100,7 +101,7 @@ public function testServerHost(): void public function testServerPort(): void { - $server = Server::getInstance(); + $server = server(); $server->set('SERVER_PORT', '9000'); $this->assertEquals('9000', $server->port()); @@ -108,7 +109,7 @@ public function testServerPort(): void public function testServerContentType(): void { - $server = Server::getInstance(); + $server = server(); $server->set('CONTENT_TYPE', 'application/json; charset=utf-8'); $this->assertEquals('application/json; charset=utf-8', $server->contentType()); @@ -117,7 +118,7 @@ public function testServerContentType(): void public function testServerReferrer(): void { - $server = Server::getInstance(); + $server = server(); $server->set('HTTP_REFERER', 'http://example.com'); $this->assertEquals('http://example.com', $server->referrer()); @@ -125,7 +126,7 @@ public function testServerReferrer(): void public function testServerAjax(): void { - $server = Server::getInstance(); + $server = server(); $server->set('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'); $this->assertTrue($server->ajax()); @@ -137,7 +138,7 @@ public function testServerAjax(): void public function testServerGetUserIpFromRemoteAddr(): void { - $server = Server::getInstance(); + $server = server(); $server->set('HTTP_CLIENT_IP', '192.168.1.1'); $server->set('HTTP_X_FORWARDED_FOR', null); @@ -162,7 +163,7 @@ public function testServerGetUserIpFromRemoteAddr(): void public function testServerGetAllHeadersFromServerClass(): void { - $server = Server::getInstance(); + $server = server(); $server->set('HTTP_USER_AGENT', 'Mozilla/5.0'); $server->set('HTTP_ACCEPT', 'text/html'); @@ -184,7 +185,7 @@ public function testServerGetAllHeadersFromServerClass(): void public function testServerAcceptedLang(): void { - $server = Server::getInstance(); + $server = server(); $server->set('HTTP_ACCEPT_LANGUAGE', null); $this->assertNull($server->acceptedLang()); diff --git a/tests/Unit/Http/Traits/Request/HttpRawInputTest.php b/tests/Unit/Http/Traits/Request/HttpRawInputTest.php index 62194452..d769e30c 100644 --- a/tests/Unit/Http/Traits/Request/HttpRawInputTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRawInputTest.php @@ -4,7 +4,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\UploadedFile; -use Quantum\Environment\Server; use Quantum\Http\Request; class HttpRawInputTest extends AppTestCase @@ -21,7 +20,7 @@ public function tearDown(): void public function testParseReturnsEmptyWhenNoBoundary(): void { - Server::getInstance()->set('CONTENT_TYPE', null); + server()->set('CONTENT_TYPE', null); $result = Request::parse('irrelevant-body'); @@ -37,7 +36,7 @@ public function testParseWithParameterBlock(): void . "JohnDoe\r\n" . "--$boundary--\r\n"; - Server::getInstance()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); + server()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); $result = Request::parse($rawInput); @@ -57,7 +56,7 @@ public function testParseWithStreamBlock(): void . "stream-data\r\n" . "--$boundary--\r\n"; - Server::getInstance()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); + server()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); $result = Request::parse($rawInput); @@ -80,7 +79,7 @@ public function testParseWithFileBlock(): void . "$fileContent\r\n" . "--$boundary--\r\n"; - Server::getInstance()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); + server()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); $result = Request::parse($rawInput); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php b/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php index 67457796..ae418a4e 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Request; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Environment\Server; use Quantum\Http\Request; class HttpRequestHeaderTest extends AppTestCase @@ -12,14 +11,14 @@ public function setUp(): void { parent::setUp(); - Request::init(Server::getInstance()); + Request::init(server()); } public function tearDown(): void { Request::flush(); - Server::getInstance()->flush(); + server()->flush(); } public function testRequestHeaderSetHasGetDelete(): void @@ -71,7 +70,7 @@ public function testGetBasicAuthCredentialsFromServer(): void { $request = new Request(); - $server = Server::getInstance(); + $server = server(); $credentials = [ 'username' => 'testGlobalUsername', @@ -133,7 +132,7 @@ public function testIsAjaxReturnsTrueFromServerWhenHeaderIsMissing(): void $this->assertFalse($request->isAjax()); - Server::getInstance()->set('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'); + server()->set('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'); $this->assertTrue($request->isAjax()); } @@ -144,7 +143,7 @@ public function testGetReferrer(): void $referrer = 'https://example.com/page'; - Server::getInstance()->set('HTTP_REFERER', $referrer); + server()->set('HTTP_REFERER', $referrer); $this->assertEquals($referrer, Request::getReferrer()); } diff --git a/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php b/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php index 973a1c6f..2aad0217 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php @@ -5,7 +5,6 @@ use Quantum\Http\Request\HttpRequest; use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\UploadedFile; -use Quantum\Environment\Server; use Quantum\Http\Request; class HttpRequestInternalTest extends AppTestCase @@ -19,7 +18,7 @@ public function testCreateRequestSetsBasicServerParams(): void { Request::create('GET', 'https://example.com/test/path?foo=bar'); - $server = Server::getInstance(); + $server = server(); $this->assertEquals('GET', $server->get('REQUEST_METHOD')); $this->assertEquals('test/path', $server->get('REQUEST_URI')); @@ -45,21 +44,21 @@ public function testContentTypeIsMultipartWhenFilesProvided(): void Request::create('POST', 'http://localhost/upload', [], [], $files); - $this->assertEquals('multipart/form-data', Server::getInstance()->get('CONTENT_TYPE')); + $this->assertEquals('multipart/form-data', server()->get('CONTENT_TYPE')); } public function testContentTypeIsFormUrlencodedWhenDataProvided(): void { Request::create('POST', 'http://localhost/form', ['key' => 'value']); - $this->assertEquals('application/x-www-form-urlencoded', Server::getInstance()->get('CONTENT_TYPE')); + $this->assertEquals('application/x-www-form-urlencoded', server()->get('CONTENT_TYPE')); } public function testContentTypeIsTextHtmlWhenNoDataOrFiles(): void { Request::create('GET', 'http://localhost'); - $this->assertEquals('text/html', Server::getInstance()->get('CONTENT_TYPE')); + $this->assertEquals('text/html', server()->get('CONTENT_TYPE')); } public function testRequestParamsAreSet(): void From 1691ced4757b31ef201313b6fe4f9e1d96e4d6fd Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:31:21 +0400 Subject: [PATCH 30/77] [#382] Migrate AssetManager singleton to DI ownership Made-with: Cursor --- src/Asset/AssetManager.php | 21 +------------------ src/Asset/Helpers/asset.php | 11 +++++----- src/Captcha/Traits/CaptchaTrait.php | 3 +-- src/View/Factories/ViewFactory.php | 3 +-- tests/Unit/Asset/AssetManagerTest.php | 2 +- .../Helpers/AssetHelperFunctionsTest.php | 2 +- .../Captcha/Adapters/HcaptchaAdapterTest.php | 3 +-- .../Captcha/Adapters/RecaptchaAdapterTest.php | 3 +-- 8 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/Asset/AssetManager.php b/src/Asset/AssetManager.php index 1c81558e..f580c956 100644 --- a/src/Asset/AssetManager.php +++ b/src/Asset/AssetManager.php @@ -17,7 +17,6 @@ namespace Quantum\Asset; use Quantum\Asset\Exceptions\AssetException; -use Quantum\Lang\Exceptions\LangException; /** * Class AssetFactory @@ -45,30 +44,13 @@ class AssetManager */ private array $published = []; - /** - * Asset instance - */ - private static ?AssetManager $instance = null; - - private function __construct() + public function __construct() { foreach (self::STORES as $type) { $this->published[$type] = []; } } - /** - * AssetManager instance - */ - public static function getInstance(): AssetManager - { - if (self::$instance == null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * Gets the asset by name */ @@ -129,7 +111,6 @@ public function flush(): void /** * Dumps the assets * @throws AssetException - * @throws LangException */ public function dump(int $type): void { diff --git a/src/Asset/Helpers/asset.php b/src/Asset/Helpers/asset.php index 659d0273..07fa47bd 100644 --- a/src/Asset/Helpers/asset.php +++ b/src/Asset/Helpers/asset.php @@ -14,22 +14,23 @@ use Quantum\Asset\Exceptions\AssetException; use Quantum\Lang\Exceptions\LangException; +use Quantum\Di\Exceptions\DiException; use Quantum\Asset\AssetManager; +use Quantum\Di\Di; /** - * Gets the AssetFactory instance + * Gets the AssetManager instance */ function asset(): AssetManager { - return AssetManager::getInstance(); + return Di::get(AssetManager::class); } /** * Dumps the assets - * @throws AssetException - * @throws LangException + * @throws AssetException|DiException|ReflectionException */ function assets(string $type): void { - AssetManager::getInstance()->dump(AssetManager::STORES[$type]); + Di::get(AssetManager::class)->dump(AssetManager::STORES[$type]); } diff --git a/src/Captcha/Traits/CaptchaTrait.php b/src/Captcha/Traits/CaptchaTrait.php index 4ebd4e15..5d0d6dc8 100644 --- a/src/Captcha/Traits/CaptchaTrait.php +++ b/src/Captcha/Traits/CaptchaTrait.php @@ -22,7 +22,6 @@ use Quantum\Http\Exceptions\HttpException; use Quantum\App\Exceptions\BaseException; use Quantum\HttpClient\HttpClient; -use Quantum\Asset\AssetManager; use Quantum\Asset\Asset; use ErrorException; use Exception; @@ -89,7 +88,7 @@ public function addToForm(string $formIdentifier = '', array $attributes = []): throw new Exception('Captcha type is not set'); } - AssetManager::getInstance()->registerAsset(new Asset(Asset::JS, static::CLIENT_API, 'captcha', -1, ['async', 'defer'])); + asset()->registerAsset(new Asset(Asset::JS, static::CLIENT_API, 'captcha', -1, ['async', 'defer'])); if (strtolower($this->type) == self::CAPTCHA_INVISIBLE) { return $this->getInvisibleElement($formIdentifier); diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index ec26da6f..930d2f57 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -22,7 +22,6 @@ use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; use DebugBar\DebugBarException; -use Quantum\Asset\AssetManager; use Quantum\Debugger\Debugger; use Quantum\View\QtView; use ReflectionException; @@ -61,7 +60,7 @@ public function resolve(): QtView if ($this->instance === null) { $this->instance = new QtView( RendererFactory::get(), - AssetManager::getInstance(), + asset(), Debugger::getInstance(), ViewCache::getInstance() ); diff --git a/tests/Unit/Asset/AssetManagerTest.php b/tests/Unit/Asset/AssetManagerTest.php index bf922e89..da3f5b48 100644 --- a/tests/Unit/Asset/AssetManagerTest.php +++ b/tests/Unit/Asset/AssetManagerTest.php @@ -16,7 +16,7 @@ public function setUp(): void config()->set('app.base_url', 'http://mydomain.com'); - $this->assetManager = AssetManager::getInstance(); + $this->assetManager = asset(); } public function testRegisterPublishDump(): void diff --git a/tests/Unit/Asset/Helpers/AssetHelperFunctionsTest.php b/tests/Unit/Asset/Helpers/AssetHelperFunctionsTest.php index c48dbbcd..37df2682 100644 --- a/tests/Unit/Asset/Helpers/AssetHelperFunctionsTest.php +++ b/tests/Unit/Asset/Helpers/AssetHelperFunctionsTest.php @@ -12,7 +12,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(AssetManager::class, 'instance', null); + asset()->flush(); } public function testAssetHelper(): void diff --git a/tests/Unit/Captcha/Adapters/HcaptchaAdapterTest.php b/tests/Unit/Captcha/Adapters/HcaptchaAdapterTest.php index 6d0b80bb..d5f33b92 100644 --- a/tests/Unit/Captcha/Adapters/HcaptchaAdapterTest.php +++ b/tests/Unit/Captcha/Adapters/HcaptchaAdapterTest.php @@ -6,7 +6,6 @@ use Quantum\Captcha\Adapters\HcaptchaAdapter; use Quantum\Tests\Unit\AppTestCase; use Quantum\HttpClient\HttpClient; -use Quantum\Asset\AssetManager; use Mockery; class HcaptchaAdapterTest extends AppTestCase @@ -31,7 +30,7 @@ public function setUp(): void public function tearDown(): void { - AssetManager::getInstance()->flush(); + asset()->flush(); Mockery::close(); } diff --git a/tests/Unit/Captcha/Adapters/RecaptchaAdapterTest.php b/tests/Unit/Captcha/Adapters/RecaptchaAdapterTest.php index 934a3d70..e9821a90 100644 --- a/tests/Unit/Captcha/Adapters/RecaptchaAdapterTest.php +++ b/tests/Unit/Captcha/Adapters/RecaptchaAdapterTest.php @@ -6,7 +6,6 @@ use Quantum\Captcha\Adapters\RecaptchaAdapter; use Quantum\Tests\Unit\AppTestCase; use Quantum\HttpClient\HttpClient; -use Quantum\Asset\AssetManager; use Exception; use Mockery; @@ -32,7 +31,7 @@ public function setUp(): void public function tearDown(): void { - AssetManager::getInstance()->flush(); + asset()->flush(); Mockery::close(); } From 507a6da52df0b6337c62a7a68d7264c29b4fc621 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:54:25 +0400 Subject: [PATCH 31/77] [#382] Migrate Csrf singleton to DI ownership Made-with: Cursor --- src/Csrf/Csrf.php | 16 +--------------- src/Csrf/Helpers/csrf.php | 3 ++- tests/Unit/Csrf/CsrfTest.php | 2 +- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Csrf/Csrf.php b/src/Csrf/Csrf.php index e4614afe..5c47589e 100644 --- a/src/Csrf/Csrf.php +++ b/src/Csrf/Csrf.php @@ -41,8 +41,6 @@ class Csrf */ public const TOKEN_KEY = 'csrf-token'; - private static ?Csrf $instance = null; - private Session $storage; private Hasher $hasher; @@ -53,24 +51,12 @@ class Csrf * @throws ReflectionException * @throws BaseException */ - private function __construct() + public function __construct() { $this->storage = session(); $this->hasher = new Hasher(); } - /** - * Csrf instance - */ - public static function getInstance(): Csrf - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * Generates the CSRF token or returns the previously generated one */ diff --git a/src/Csrf/Helpers/csrf.php b/src/Csrf/Helpers/csrf.php index ccce8dba..5a1d6f4c 100644 --- a/src/Csrf/Helpers/csrf.php +++ b/src/Csrf/Helpers/csrf.php @@ -15,13 +15,14 @@ use Quantum\App\Exceptions\BaseException; use Quantum\App\Exceptions\AppException; use Quantum\Csrf\Csrf; +use Quantum\Di\Di; /** * Gets the Csrf instance */ function csrf(): Csrf { - return Csrf::getInstance(); + return Di::get(Csrf::class); } /** diff --git a/tests/Unit/Csrf/CsrfTest.php b/tests/Unit/Csrf/CsrfTest.php index 010856c6..26178ace 100644 --- a/tests/Unit/Csrf/CsrfTest.php +++ b/tests/Unit/Csrf/CsrfTest.php @@ -20,7 +20,7 @@ public function setUp(): void $this->request = new Request(); - $this->csrf = Csrf::getInstance(); + $this->csrf = csrf(); } public function testGenerateToken(): void From b3ee4fc1f42a873fa35e90e7e03a401566783181 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:53:58 +0400 Subject: [PATCH 32/77] [#382] Migrate Database singleton to DI ownership Made-with: Cursor --- src/Database/Database.php | 19 +----------- src/Database/Traits/RelationalTrait.php | 4 ++- src/Database/Traits/TransactionTrait.php | 11 ++++--- src/Migration/MigrationManager.php | 29 +++++-------------- src/Model/Factories/ModelFactory.php | 3 +- .../Adapters/Idiorm/IdiormDbalTestCase.php | 5 ++-- .../Adapters/Sleekdb/SleekDbalTestCase.php | 5 ++-- tests/Unit/Database/DatabaseTest.php | 15 +++++----- tests/Unit/Model/DbModelTest.php | 4 ++- tests/Unit/Model/DbModelTimestampsTest.php | 2 ++ .../Unit/Model/ModelSoftDeletesIdiOrmTest.php | 4 ++- .../Unit/Model/ModelSoftDeletesSleekTest.php | 7 ++--- tests/Unit/Paginator/PaginatorTestCase.php | 5 ++-- 13 files changed, 46 insertions(+), 67 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index c0a1e546..0b6c2c1a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -50,11 +50,6 @@ class Database */ private $configs; - /** - * Database instance - */ - private static ?Database $instance = null; - private string $ormClass; /** @@ -63,7 +58,7 @@ class Database * @throws DiException * @throws ReflectionException */ - private function __construct() + public function __construct() { if (!config()->has('database')) { config()->import(new Setup('config', 'database')); @@ -80,18 +75,6 @@ private function __construct() } } - /** - * Get Instance - */ - public static function getInstance(): Database - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * Gets the ORM class */ diff --git a/src/Database/Traits/RelationalTrait.php b/src/Database/Traits/RelationalTrait.php index 2b2f0537..bc30e00e 100644 --- a/src/Database/Traits/RelationalTrait.php +++ b/src/Database/Traits/RelationalTrait.php @@ -17,6 +17,8 @@ namespace Quantum\Database\Traits; use Quantum\Database\Exceptions\DatabaseException; +use Quantum\Database\Database; +use Quantum\Di\Di; /** * Trait RelationalTrait @@ -84,6 +86,6 @@ public static function queryLog(): array */ protected static function resolveQuery(string $method, string $query = '', array $parameters = []) { - return self::getInstance()->getOrmClass()::$method($query, $parameters); + return Di::get(Database::class)->getOrmClass()::$method($query, $parameters); } } diff --git a/src/Database/Traits/TransactionTrait.php b/src/Database/Traits/TransactionTrait.php index d243e7cf..3ea6f08e 100644 --- a/src/Database/Traits/TransactionTrait.php +++ b/src/Database/Traits/TransactionTrait.php @@ -18,6 +18,9 @@ use Quantum\Database\Exceptions\DatabaseException; use Quantum\App\Exceptions\BaseException; +use Quantum\Database\Database; +use ReflectionException; +use Quantum\Di\Di; use Throwable; /** @@ -28,7 +31,7 @@ trait TransactionTrait { /** * Begins a transaction - * @throws BaseException + * @throws BaseException|ReflectionException */ public static function beginTransaction(): void { @@ -47,7 +50,7 @@ public static function commit(): void /** * Rolls back a transaction - * @throws BaseException + * @throws BaseException|ReflectionException */ public static function rollback(): void { @@ -57,11 +60,11 @@ public static function rollback(): void /** * Resolves the transaction method call * @return mixed - * @throws BaseException + * @throws BaseException|ReflectionException */ protected static function resolveTransaction(string $method) { - $db = self::getInstance()->getOrmClass(); + $db = Di::get(Database::class)->getOrmClass(); if (!method_exists($db, $method)) { throw DatabaseException::methodNotSupported($method, self::class); diff --git a/src/Migration/MigrationManager.php b/src/Migration/MigrationManager.php index 34e70754..16991e18 100644 --- a/src/Migration/MigrationManager.php +++ b/src/Migration/MigrationManager.php @@ -23,12 +23,12 @@ use Quantum\Storage\Factories\FileSystemFactory; use Quantum\Config\Exceptions\ConfigException; use Quantum\Database\Factories\TableFactory; -use Quantum\Lang\Exceptions\LangException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\Storage\FileSystem; use Quantum\Database\Database; use ReflectionException; +use Quantum\Di\Di; /** * Class MigrationManager @@ -71,17 +71,13 @@ class MigrationManager private Database $db; /** - * @throws BaseException - * @throws DiException - * @throws FileSystemException - * @throws ConfigException - * @throws ReflectionException + * @throws BaseException|ConfigException|DiException|FileSystemException|ReflectionException */ public function __construct() { $this->fs = FileSystemFactory::get(); - $this->db = Database::getInstance(); + $this->db = Di::get(Database::class); $this->tableFactory = new TableFactory(); @@ -95,7 +91,6 @@ public function __construct() /** * Generates new migration file * @throws MigrationException - * @throws LangException */ public function generateMigration(string $table, string $action): string { @@ -114,10 +109,7 @@ public function generateMigration(string $table, string $action): string /** * Applies migrations - * @throws BaseException - * @throws DatabaseException - * @throws LangException - * @throws MigrationException + * @throws MigrationException|DatabaseException */ public function applyMigrations(string $direction, ?int $step = null): ?int { @@ -144,8 +136,7 @@ public function applyMigrations(string $direction, ?int $step = null): ?int /** * Runs up migrations - * @throws DatabaseException - * @throws MigrationException + * @throws MigrationException|DatabaseException */ private function upgrade(): int { @@ -182,8 +173,7 @@ private function upgrade(): int /** * Runs down migrations - * @throws DatabaseException - * @throws MigrationException + * @throws MigrationException|DatabaseException */ private function downgrade(?int $step): int { @@ -222,9 +212,7 @@ private function downgrade(?int $step): int /** * Prepares up migrations - * @throws MigrationException - * @throws DatabaseException - * + * @throws MigrationException|DatabaseException */ private function prepareUpMigrations(): void { @@ -249,8 +237,7 @@ private function prepareUpMigrations(): void /** * Prepares down migrations - * @throws DatabaseException - * @throws MigrationException + * @throws MigrationException|DatabaseException */ private function prepareDownMigrations(?int $step = null): void { diff --git a/src/Model/Factories/ModelFactory.php b/src/Model/Factories/ModelFactory.php index b0f75189..9db63b00 100644 --- a/src/Model/Factories/ModelFactory.php +++ b/src/Model/Factories/ModelFactory.php @@ -21,6 +21,7 @@ use Quantum\Database\Database; use Quantum\Model\DbModel; use Quantum\Model\Model; +use Quantum\Di\Di; /** * Class ModelFactory @@ -100,7 +101,7 @@ protected static function createOrmInstance( array $foreignKeys = [], array $hidden = [] ): DbalInterface { - $ormClass = Database::getInstance()->getOrmClass(); + $ormClass = Di::get(Database::class)->getOrmClass(); $instance = new $ormClass( $table, diff --git a/tests/Unit/Database/Adapters/Idiorm/IdiormDbalTestCase.php b/tests/Unit/Database/Adapters/Idiorm/IdiormDbalTestCase.php index 56d4f221..f06a353d 100644 --- a/tests/Unit/Database/Adapters/Idiorm/IdiormDbalTestCase.php +++ b/tests/Unit/Database/Adapters/Idiorm/IdiormDbalTestCase.php @@ -5,7 +5,6 @@ use Quantum\Tests\Unit\Database\Adapters\DatabaseSeeder; use Quantum\Database\Adapters\Idiorm\IdiormDbal; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Database\Database; abstract class IdiormDbalTestCase extends AppTestCase { @@ -23,8 +22,6 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(Database::class, 'instance', null); - config()->set('app.debug', true); IdiormDbal::connect(['driver' => 'sqlite', 'database' => ':memory:']); @@ -41,6 +38,8 @@ public function tearDown(): void $this->deleteTables(); IdiormDbal::disconnect(); + + parent::tearDown(); } private function createTables(): void diff --git a/tests/Unit/Database/Adapters/Sleekdb/SleekDbalTestCase.php b/tests/Unit/Database/Adapters/Sleekdb/SleekDbalTestCase.php index 6e87bc1e..f8f58277 100644 --- a/tests/Unit/Database/Adapters/Sleekdb/SleekDbalTestCase.php +++ b/tests/Unit/Database/Adapters/Sleekdb/SleekDbalTestCase.php @@ -5,7 +5,6 @@ use Quantum\Tests\Unit\Database\Adapters\DatabaseSeeder; use Quantum\Database\Adapters\Sleekdb\SleekDbal; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Database\Database; use Quantum\Loader\Setup; abstract class SleekDbalTestCase extends AppTestCase @@ -25,8 +24,6 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(Database::class, 'instance', null); - if (!config()->has('database')) { config()->import(new Setup('config', 'database')); } @@ -45,6 +42,8 @@ public function tearDown(): void $this->deleteTables(); SleekDbal::disconnect(); + + parent::tearDown(); } public function deleteTables(): void diff --git a/tests/Unit/Database/DatabaseTest.php b/tests/Unit/Database/DatabaseTest.php index dff5475f..67da7922 100644 --- a/tests/Unit/Database/DatabaseTest.php +++ b/tests/Unit/Database/DatabaseTest.php @@ -6,6 +6,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Database\Database; use Quantum\Loader\Setup; +use Quantum\Di\Di; class DatabaseTest extends AppTestCase { @@ -21,8 +22,6 @@ public function setUp(): void config()->set('debug', true); - $this->setPrivateProperty(Database::class, 'instance', null); - Database::execute('CREATE TABLE IF NOT EXISTS profiles ( id INTEGER PRIMARY KEY, firstname VARCHAR(255), @@ -36,13 +35,15 @@ public function setUp(): void public function tearDown(): void { Database::execute('DROP TABLE IF EXISTS profiles'); + + parent::tearDown(); } - public function testDatabaseInstance(): void + public function testDatabaseDiReturnsSameInstance(): void { - $db1 = Database::getInstance(); + $db1 = Di::get(Database::class); - $db2 = Database::getInstance(); + $db2 = Di::get(Database::class); $this->assertInstanceOf(Database::class, $db1); @@ -51,12 +52,12 @@ public function testDatabaseInstance(): void public function testDatabaseGetConfigs(): void { - $this->assertEquals(config()->get('database.sqlite'), Database::getInstance()->getConfigs()); + $this->assertEquals(config()->get('database.sqlite'), Di::get(Database::class)->getConfigs()); } public function testDatabaseGetOrmClass(): void { - $this->assertEquals(IdiormDbal::class, Database::getInstance()->getOrmClass()); + $this->assertEquals(IdiormDbal::class, Di::get(Database::class)->getOrmClass()); } public function testDatabaseRawQueries(): void diff --git a/tests/Unit/Model/DbModelTest.php b/tests/Unit/Model/DbModelTest.php index 83e9d6c4..5421ddf7 100644 --- a/tests/Unit/Model/DbModelTest.php +++ b/tests/Unit/Model/DbModelTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\Model; -use Quantum\Model\Model; use Quantum\Tests\_root\shared\Models\TestProfileModel; use Quantum\Database\Adapters\Idiorm\IdiormDbal; use Quantum\Model\Exceptions\ModelException; @@ -11,6 +10,7 @@ use Quantum\Model\ModelCollection; use Quantum\Paginator\Paginator; use Quantum\Model\DbModel; +use Quantum\Model\Model; class DbModelTest extends AppTestCase { @@ -32,6 +32,8 @@ public function setUp(): void public function tearDown(): void { IdiormDbal::execute('DROP TABLE profiles'); + + parent::tearDown(); } public function testDbModelInstance(): void diff --git a/tests/Unit/Model/DbModelTimestampsTest.php b/tests/Unit/Model/DbModelTimestampsTest.php index e90067db..b1e388b5 100644 --- a/tests/Unit/Model/DbModelTimestampsTest.php +++ b/tests/Unit/Model/DbModelTimestampsTest.php @@ -27,6 +27,8 @@ public function tearDown(): void { IdiormDbal::execute('DROP TABLE posts'); IdiormDbal::execute('DROP TABLE posts_custom'); + + parent::tearDown(); } public function testTimestampsAreNotAppliedWhenTraitIsNotUsed(): void diff --git a/tests/Unit/Model/ModelSoftDeletesIdiOrmTest.php b/tests/Unit/Model/ModelSoftDeletesIdiOrmTest.php index 0a8df16e..cb3498fa 100644 --- a/tests/Unit/Model/ModelSoftDeletesIdiOrmTest.php +++ b/tests/Unit/Model/ModelSoftDeletesIdiOrmTest.php @@ -2,13 +2,13 @@ namespace Quantum\Tests\Unit\Model; -use Quantum\Model\Model; use Quantum\Tests\_root\shared\Models\TestProductsModel; use Quantum\Database\Adapters\Idiorm\IdiormDbal; use Quantum\Model\Factories\ModelFactory; use Quantum\Tests\Unit\AppTestCase; use Quantum\Model\ModelCollection; use Quantum\Paginator\Paginator; +use Quantum\Model\Model; class ModelSoftDeletesIdiOrmTest extends AppTestCase { @@ -30,6 +30,8 @@ public function setUp(): void public function tearDown(): void { IdiormDbal::execute('DROP TABLE products '); + + parent::tearDown(); } public function testDeleteSetsDeletedAt(): void diff --git a/tests/Unit/Model/ModelSoftDeletesSleekTest.php b/tests/Unit/Model/ModelSoftDeletesSleekTest.php index 913ffbae..df9cb667 100644 --- a/tests/Unit/Model/ModelSoftDeletesSleekTest.php +++ b/tests/Unit/Model/ModelSoftDeletesSleekTest.php @@ -2,15 +2,14 @@ namespace Quantum\Tests\Unit\Model; -use Quantum\Model\Model; use Quantum\Tests\_root\shared\Models\TestProductsModel; use Quantum\Database\Adapters\Sleekdb\SleekDbal; use Quantum\Model\Factories\ModelFactory; use Quantum\Tests\Unit\AppTestCase; use Quantum\Model\ModelCollection; use Quantum\Paginator\Paginator; -use Quantum\Database\Database; use Quantum\Loader\Setup; +use Quantum\Model\Model; class ModelSoftDeletesSleekTest extends AppTestCase { @@ -20,8 +19,6 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(Database::class, 'instance', null); - if (!config()->has('database')) { config()->import(new Setup('config', 'database')); } @@ -38,6 +35,8 @@ public function setUp(): void public function tearDown(): void { ModelFactory::get(TestProductsModel::class)->truncate(); + + parent::tearDown(); } public function testSleekDeleteSetsDeletedAt(): void diff --git a/tests/Unit/Paginator/PaginatorTestCase.php b/tests/Unit/Paginator/PaginatorTestCase.php index 8303c835..381ea027 100644 --- a/tests/Unit/Paginator/PaginatorTestCase.php +++ b/tests/Unit/Paginator/PaginatorTestCase.php @@ -4,7 +4,6 @@ use Quantum\Database\Adapters\Idiorm\IdiormDbal; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Database\Database; class PaginatorTestCase extends AppTestCase { @@ -12,8 +11,6 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(Database::class, 'instance', null); - IdiormDbal::connect(['driver' => 'sqlite', 'database' => ':memory:']); $this->_createPostTableWithData(); @@ -22,6 +19,8 @@ public function setUp(): void public function tearDown(): void { IdiormDbal::execute('DROP TABLE IF EXISTS posts'); + + parent::tearDown(); } private function _createPostTableWithData(): void From 5793234e3a3c523e2b3e031f815ac08ffdf91c33 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:04:40 +0400 Subject: [PATCH 33/77] [#382] Add unit tests for Csrf helper functions Made-with: Cursor --- .../Csrf/Helpers/CsrfHelperFunctionsTest.php | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Csrf/Helpers/CsrfHelperFunctionsTest.php b/tests/Unit/Csrf/Helpers/CsrfHelperFunctionsTest.php index 167eea02..8a97561b 100644 --- a/tests/Unit/Csrf/Helpers/CsrfHelperFunctionsTest.php +++ b/tests/Unit/Csrf/Helpers/CsrfHelperFunctionsTest.php @@ -3,22 +3,56 @@ namespace Quantum\Tests\Unit\Csrf\Helpers; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Request; use Quantum\Csrf\Csrf; class CsrfHelperFunctionsTest extends AppTestCase { - public function testCsrfHelper(): void + private string $key = '#321dMd3QS15%'; + + public function setUp(): void + { + parent::setUp(); + + session()->delete(Csrf::TOKEN_KEY); + } + + public function testCsrfHelperReturnsInstance(): void { $this->assertInstanceOf(Csrf::class, csrf()); } - public function testCsrfToken(): void + public function testCsrfHelperReturnsSameInstance(): void { - $request = new Request(); + $this->assertSame(csrf(), csrf()); + } + + public function testCsrfTokenGeneratesToken(): void + { + $token = csrf()->generateToken($this->key); + + $this->assertNotEmpty($token); + + $this->assertIsString($token); - $request->create('PUT', '/update', ['title' => 'Task Title', 'csrf-token' => csrf_token()]); + $this->assertTrue(session()->has(Csrf::TOKEN_KEY)); + } + + public function testCsrfTokenHelperGeneratesToken(): void + { + $token = csrf_token(); + + $this->assertNotEmpty($token); + + $this->assertIsString($token); + + $this->assertTrue(session()->has(Csrf::TOKEN_KEY)); + } + + public function testCsrfTokenHelperReturnsSameTokenOnSubsequentCalls(): void + { + $token1 = csrf_token(); + $token2 = csrf_token(); - $this->assertTrue(csrf()->checkToken($request)); + $this->assertSame($token1, $token2); } } From 375933382fd74c359571aadb107ae45c4a0995e8 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:24:30 +0400 Subject: [PATCH 34/77] [#382] Migrate MailTrap singleton to DI ownership Made-with: Cursor --- src/Mailer/MailTrap.php | 16 +--------------- src/Mailer/Traits/MailerTrait.php | 3 ++- .../Toolkit/src/Services/EmailService.php.tpl | 7 ++++--- tests/Unit/Mailer/MailTrapTest.php | 7 +++++-- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/Mailer/MailTrap.php b/src/Mailer/MailTrap.php index dd275c56..67ff8efe 100644 --- a/src/Mailer/MailTrap.php +++ b/src/Mailer/MailTrap.php @@ -42,8 +42,6 @@ class MailTrap */ private $message; - private static ?MailTrap $instance = null; - private string $emailsDirectory; /** @@ -52,25 +50,13 @@ class MailTrap * @throws DiException * @throws ReflectionException */ - private function __construct() + public function __construct() { $this->fs = FileSystemFactory::get(); $this->parser = new MessageParser(); $this->emailsDirectory = base_dir() . DS . 'shared' . DS . 'emails'; } - /** - * Get Instance - */ - public static function getInstance(): MailTrap - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * Saves the message on the local file */ diff --git a/src/Mailer/Traits/MailerTrait.php b/src/Mailer/Traits/MailerTrait.php index 9f8d0e0d..e9118509 100644 --- a/src/Mailer/Traits/MailerTrait.php +++ b/src/Mailer/Traits/MailerTrait.php @@ -21,6 +21,7 @@ use Quantum\Debugger\Debugger; use Quantum\Mailer\MailTrap; use ReflectionException; +use Quantum\Di\Di; use Exception; /** @@ -266,7 +267,7 @@ private function saveEmail(): bool return false; } - return MailTrap::getInstance()->saveMessage($messageId, $this->getMessageContent()); + return Di::get(MailTrap::class)->saveMessage($messageId, $this->getMessageContent()); } /** diff --git a/src/Module/Templates/Toolkit/src/Services/EmailService.php.tpl b/src/Module/Templates/Toolkit/src/Services/EmailService.php.tpl index 67167d5a..df3a33b8 100644 --- a/src/Module/Templates/Toolkit/src/Services/EmailService.php.tpl +++ b/src/Module/Templates/Toolkit/src/Services/EmailService.php.tpl @@ -19,10 +19,11 @@ use Quantum\Storage\Exceptions\FileSystemException; use Quantum\Paginator\Factories\PaginatorFactory; use Quantum\Paginator\Enums\PaginatorType; use Quantum\App\Exceptions\BaseException; -use Quantum\Mailer\MailTrap; use Quantum\Paginator\Paginator; use Quantum\Service\QtService; +use Quantum\Mailer\MailTrap; use ReflectionException; +use Quantum\Di\Di; /** * Class EmailService @@ -50,7 +51,7 @@ class EmailService extends QtService */ public function getEmails(int $perPage, int $currentPage): Paginator { - $mailTrap = MailTrap::getInstance(); + $mailTrap = Di::get(MailTrap::class); $emailFiles = fs()->listDirectory($this->emailsDirectory); @@ -87,7 +88,7 @@ class EmailService extends QtService */ public function getEmail(string $emailId): MailTrap { - $mailTrap = MailTrap::getInstance(); + $mailTrap = Di::get(MailTrap::class); return $mailTrap->parseMessage($emailId); } diff --git a/tests/Unit/Mailer/MailTrapTest.php b/tests/Unit/Mailer/MailTrapTest.php index c62d1086..c9d6a348 100644 --- a/tests/Unit/Mailer/MailTrapTest.php +++ b/tests/Unit/Mailer/MailTrapTest.php @@ -2,8 +2,9 @@ namespace Quantum\Tests\Unit\Mailer; -use Quantum\Mailer\MailTrap; use Quantum\Tests\Unit\AppTestCase; +use Quantum\Mailer\MailTrap; +use Quantum\Di\Di; class MailTrapTest extends AppTestCase { @@ -19,7 +20,7 @@ public function setUp(): void { parent::setUp(); - $this->mailTrap = MailTrap::getInstance(); + $this->mailTrap = Di::get(MailTrap::class); $this->filename = '2YILSA4zZk61tDdYEfGMw7lNznlhAQakjwNGr0QCq44'; @@ -62,6 +63,8 @@ public function setUp(): void public function tearDown(): void { $this->fs->remove($this->path . DS . $this->filename . '.eml'); + + parent::tearDown(); } public function testMailTrapInstance(): void From 530f433863602bc6aaa8bf969581ad5f7d1c487c Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:46:08 +0400 Subject: [PATCH 35/77] [#382] Migrate Debugger singleton to DI ownership Made-with: Cursor --- src/App/Adapters/WebAppAdapter.php | 2 +- src/App/Traits/WebAppTrait.php | 15 ++++------ src/Debugger/Debugger.php | 29 ++++--------------- src/Logger/Adapters/MessageAdapter.php | 9 +++++- src/View/Factories/ViewFactory.php | 2 +- src/View/Helpers/view.php | 3 +- tests/Unit/AppTestCase.php | 4 ++- tests/Unit/Debugger/DebuggerTest.php | 4 +-- .../Logger/Adapters/MessageAdapterTest.php | 6 ++-- .../Helpers/LoggerHelperFunctionsTest.php | 7 ++--- tests/Unit/Logger/LoggerTest.php | 6 ++-- 11 files changed, 33 insertions(+), 54 deletions(-) diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 9d88bf0c..d954082c 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -141,7 +141,7 @@ public function start(): ?int (new LoadLanguageStage())->process($this->context); - $debugger = Debugger::getInstance(); + $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { $debugger->addToStoreCell(Debugger::HOOKS, 'info', HookManager::getInstance()->getRegistered()); } diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index e3d58e87..2d49e52c 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -22,12 +22,12 @@ use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; -use DebugBar\DebugBarException; use Quantum\Debugger\Debugger; use Quantum\Http\Response; use Quantum\Http\Request; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Trait WebAppTrait @@ -36,10 +36,7 @@ trait WebAppTrait { /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws HttpException + * @throws BaseException|HttpException|DiException|ReflectionException */ private function initializeRequestResponse(Request $request, Response $response): void { @@ -48,11 +45,11 @@ private function initializeRequestResponse(Request $request, Response $response) } /** - * @throws DebugBarException + * @throws DiException|ReflectionException */ private function initializeDebugger(): void { - $debugger = Debugger::getInstance(); + $debugger = Di::get(Debugger::class); $debugger->initStore(); } @@ -73,9 +70,7 @@ private function setupViewCache(): ViewCache } /** - * @throws ConfigException - * @throws DiException - * @throws ReflectionException|LoaderException + * @throws ConfigException|LoaderException|DiException|ReflectionException */ private function handleCors(Response $response): void { diff --git a/src/Debugger/Debugger.php b/src/Debugger/Debugger.php index 8f2419e6..457b1999 100644 --- a/src/Debugger/Debugger.php +++ b/src/Debugger/Debugger.php @@ -62,8 +62,6 @@ class Debugger */ private DebuggerStore $store; - private static ?Debugger $instance = null; - /** * DebugBar instance */ @@ -80,37 +78,21 @@ class Debugger private string $customCss = 'custom_debugbar.css'; /** - * Debugger constructor. * @param array $collectors * @throws DebugBarException */ - public function __construct(DebuggerStore $store, DebugBar $debugBar, array $collectors = []) + public function __construct(?DebuggerStore $store = null, ?DebugBar $debugBar = null, array $collectors = []) { - $this->store = $store; - $this->debugBar = $debugBar; + $this->store = $store ?? new DebuggerStore(); + $this->debugBar = $debugBar ?? new DebugBar(); + + $collectors = $collectors ?: self::getDefaultCollectors(); foreach ($collectors as $collector) { $this->debugBar->addCollector($collector); } } - /** - * @param array $collectors - * @throws DebugBarException - */ - public static function getInstance(?DebuggerStore $store = null, ?DebugBar $debugBar = null, ?array $collectors = []): Debugger - { - if (self::$instance === null) { - $debugBar ??= new DebugBar(); - $store ??= new DebuggerStore(); - $collectors = $collectors ?: self::getDefaultCollectors(); - - self::$instance = new self($store, $debugBar, $collectors); - } - - return self::$instance; - } - /** * Checks if debug bar enabled */ @@ -179,7 +161,6 @@ public function render(): string /** * Creates a tab - * @return void * @throws DebugBarException */ protected function createTab(string $type): void diff --git a/src/Logger/Adapters/MessageAdapter.php b/src/Logger/Adapters/MessageAdapter.php index 15ac4a3f..687e011f 100644 --- a/src/Logger/Adapters/MessageAdapter.php +++ b/src/Logger/Adapters/MessageAdapter.php @@ -16,9 +16,12 @@ namespace Quantum\Logger\Adapters; +use Quantum\Di\Exceptions\DiException; use Quantum\Logger\Contracts\ReportableInterface; use DebugBar\DebugBarException; use Quantum\Debugger\Debugger; +use Quantum\Di\Di; +use ReflectionException; /** * Class MessageAdapter @@ -30,11 +33,15 @@ class MessageAdapter implements ReportableInterface * @param array|null $context * @throws DebugBarException */ + + /** + * @throws DiException|ReflectionException + */ public function report(string $level, string $message, ?array $context = []): void { $tab = $context['tab'] ?? Debugger::MESSAGES; - $debugger = Debugger::getInstance(); + $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { $debugger->addToStoreCell($tab, $level, $message); diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index 930d2f57..182f2b1b 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -61,7 +61,7 @@ public function resolve(): QtView $this->instance = new QtView( RendererFactory::get(), asset(), - Debugger::getInstance(), + Di::get(Debugger::class), ViewCache::getInstance() ); } diff --git a/src/View/Helpers/view.php b/src/View/Helpers/view.php index a0721730..0adfe09e 100644 --- a/src/View/Helpers/view.php +++ b/src/View/Helpers/view.php @@ -23,6 +23,7 @@ use Quantum\Di\Exceptions\DiException; use DebugBar\DebugBarException; use Quantum\Debugger\Debugger; +use Quantum\Di\Di; use Quantum\View\RawParam; /** @@ -82,7 +83,7 @@ function raw_param($value): RawParam */ function debugbar(): ?string { - $debugger = Debugger::getInstance(); + $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { return $debugger->render(); diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index af93aeed..8afee0a0 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -41,7 +41,9 @@ public function tearDown(): void if (Di::isRegistered(Config::class)) { config()->flush(); } - Debugger::getInstance()->resetStore(); + if (Di::isRegistered(Debugger::class)) { + Di::get(Debugger::class)->resetStore(); + } Di::reset(); } diff --git a/tests/Unit/Debugger/DebuggerTest.php b/tests/Unit/Debugger/DebuggerTest.php index e1bcb4aa..5300c794 100644 --- a/tests/Unit/Debugger/DebuggerTest.php +++ b/tests/Unit/Debugger/DebuggerTest.php @@ -40,9 +40,7 @@ public function setUp(): void ->with($collector); } - $this->setPrivateProperty(Debugger::class, 'instance', null); - - $this->debugger = Debugger::getInstance($this->debuggerStore, $this->debugBarMock, $collectors); + $this->debugger = new Debugger($this->debuggerStore, $this->debugBarMock, $collectors); } public function testDebuggerIsEnabled(): void diff --git a/tests/Unit/Logger/Adapters/MessageAdapterTest.php b/tests/Unit/Logger/Adapters/MessageAdapterTest.php index 844d8897..dc5644df 100644 --- a/tests/Unit/Logger/Adapters/MessageAdapterTest.php +++ b/tests/Unit/Logger/Adapters/MessageAdapterTest.php @@ -3,9 +3,9 @@ namespace Quantum\Tests\Unit\Logger\Adapters; use Quantum\Logger\Adapters\MessageAdapter; -use Quantum\Debugger\DebuggerStore; use Quantum\Tests\Unit\AppTestCase; use Quantum\Debugger\Debugger; +use Quantum\Di\Di; class MessageAdapterTest extends AppTestCase { @@ -16,9 +16,7 @@ public function setUp(): void { parent::setUp(); - $store = new DebuggerStore(); - - $this->debugger = Debugger::getInstance($store); + $this->debugger = Di::get(Debugger::class); $this->debugger->initStore(); diff --git a/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php b/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php index e7d6713e..cc1468b1 100644 --- a/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php +++ b/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php @@ -7,10 +7,10 @@ use Quantum\Logger\Adapters\SingleAdapter; use Quantum\Logger\Adapters\DailyAdapter; use Quantum\Logger\Enums\LoggerType; -use Quantum\Debugger\DebuggerStore; use Quantum\Tests\Unit\AppTestCase; use Quantum\Debugger\Debugger; use Quantum\Logger\Logger; +use Quantum\Di\Di; class LoggerHelperFunctionsTest extends AppTestCase { @@ -22,9 +22,7 @@ public function setUp(): void config()->set('app.debug', true); - $store = new DebuggerStore(); - - $this->debugger = Debugger::getInstance($store); + $this->debugger = Di::get(Debugger::class); $this->debugger->resetStore(); } @@ -32,6 +30,7 @@ public function setUp(): void public function tearDown(): void { $this->debugger->resetStore(); + parent::tearDown(); } public function testLoggerHelperGetDefaultLoggerAdapter(): void diff --git a/tests/Unit/Logger/LoggerTest.php b/tests/Unit/Logger/LoggerTest.php index 422546a5..c5a03da7 100644 --- a/tests/Unit/Logger/LoggerTest.php +++ b/tests/Unit/Logger/LoggerTest.php @@ -4,10 +4,10 @@ use Quantum\Logger\Contracts\ReportableInterface; use Quantum\Logger\Adapters\MessageAdapter; -use Quantum\Debugger\DebuggerStore; use Quantum\Tests\Unit\AppTestCase; use Quantum\Debugger\Debugger; use Quantum\Logger\Logger; +use Quantum\Di\Di; class LoggerTest extends AppTestCase { @@ -19,9 +19,7 @@ public function setUp(): void { parent::setUp(); - $store = new DebuggerStore(); - - $this->debugger = Debugger::getInstance($store); + $this->debugger = Di::get(Debugger::class); $this->debugger->initStore(); From e326da77989d257dc8527fbaee4f3cfc1322a074 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 8 Apr 2026 20:14:49 +0400 Subject: [PATCH 36/77] [#382] Migrate ViewCache singleton to DI ownership Made-with: Cursor --- src/App/Traits/WebAppTrait.php | 2 +- src/ResourceCache/ViewCache.php | 47 +++++----------------- src/View/Factories/ViewFactory.php | 15 ++----- src/View/Helpers/view.php | 7 +--- tests/Unit/ResourceCache/ViewCacheTest.php | 4 +- 5 files changed, 18 insertions(+), 57 deletions(-) diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index 2d49e52c..cd296f7f 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -60,7 +60,7 @@ private function initializeDebugger(): void */ private function setupViewCache(): ViewCache { - $viewCache = ViewCache::getInstance(); + $viewCache = Di::get(ViewCache::class); if ($viewCache->isEnabled()) { $viewCache->setup(); diff --git a/src/ResourceCache/ViewCache.php b/src/ResourceCache/ViewCache.php index 35fe7551..5e8a0351 100644 --- a/src/ResourceCache/ViewCache.php +++ b/src/ResourceCache/ViewCache.php @@ -16,11 +16,9 @@ namespace Quantum\ResourceCache; -use Quantum\Loader\Exceptions\LoaderException; use Quantum\ResourceCache\Exceptions\ResourceCacheException; -use Quantum\Database\Exceptions\DatabaseException; -use Quantum\Session\Exceptions\SessionException; use Quantum\Storage\Factories\FileSystemFactory; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; @@ -39,10 +37,7 @@ class ViewCache { private ?string $cacheDir = null; - /** - * @var int - */ - private $ttl = 300; + private int $ttl = 300; private bool $isEnabled; @@ -50,17 +45,6 @@ class ViewCache private FileSystem $fs; - private static ?ViewCache $instance = null; - - public static function getInstance(): ViewCache - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * @throws DiException * @throws Exception @@ -83,7 +67,7 @@ public function setup(): void $this->cacheDir = $this->getCacheDir(); - $this->ttl = config()->get('view_cache.ttl', $this->ttl); + $this->ttl = (int) config()->get('view_cache.ttl', $this->ttl); $this->minification = filter_var(config()->get('view_cache.minify', $this->minification), FILTER_VALIDATE_BOOLEAN); @@ -93,12 +77,7 @@ public function setup(): void } /** - * @throws BaseException - * @throws ConfigException - * @throws DatabaseException - * @throws DiException - * @throws ReflectionException - * @throws SessionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function serveCachedView(string $uri, Response $response): bool { @@ -114,10 +93,7 @@ public function serveCachedView(string $uri, Response $response): bool } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function set(string $key, string $content): ViewCache { @@ -131,12 +107,7 @@ public function set(string $key, string $content): ViewCache } /** - * @throws BaseException - * @throws ConfigException - * @throws DatabaseException - * @throws DiException - * @throws ReflectionException - * @throws SessionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function get(string $key): ?string { @@ -196,9 +167,6 @@ public function enableMinification(bool $state): void $this->minification = $state; } - /** - * @param $cacheFile - */ private function isExpired(string $cacheFile): bool { if (time() > ($this->fs->lastModified($cacheFile) + $this->ttl)) { @@ -209,6 +177,9 @@ private function isExpired(string $cacheFile): bool return false; } + /** + * @throws DiException|ReflectionException + */ private function getCacheDir(): string { $configCacheDir = config()->get('view_cache.cache_dir', 'cache'); diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index 182f2b1b..14c57ab0 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -21,7 +21,6 @@ use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; -use DebugBar\DebugBarException; use Quantum\Debugger\Debugger; use Quantum\View\QtView; use ReflectionException; @@ -37,11 +36,7 @@ class ViewFactory private ?QtView $instance = null; /** - * @throws DebugBarException - * @throws DiException - * @throws BaseException - * @throws ConfigException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public static function get(): QtView { @@ -49,11 +44,7 @@ public static function get(): QtView } /** - * @throws DebugBarException - * @throws DiException - * @throws BaseException - * @throws ConfigException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public function resolve(): QtView { @@ -62,7 +53,7 @@ public function resolve(): QtView RendererFactory::get(), asset(), Di::get(Debugger::class), - ViewCache::getInstance() + Di::get(ViewCache::class) ); } diff --git a/src/View/Helpers/view.php b/src/View/Helpers/view.php index 0adfe09e..25f5d15f 100644 --- a/src/View/Helpers/view.php +++ b/src/View/Helpers/view.php @@ -23,14 +23,13 @@ use Quantum\Di\Exceptions\DiException; use DebugBar\DebugBarException; use Quantum\Debugger\Debugger; -use Quantum\Di\Di; use Quantum\View\RawParam; +use Quantum\Di\Di; /** * Rendered view * @throws BaseException * @throws ConfigException - * @throws DebugBarException * @throws DiException * @throws ReflectionException * @throws ViewException @@ -45,7 +44,6 @@ function view(): ?string * @param array $args * @throws BaseException * @throws ConfigException - * @throws DebugBarException * @throws DiException * @throws ReflectionException */ @@ -59,7 +57,6 @@ function partial(string $partial, array $args = []): string * @return mixed|null * @throws BaseException * @throws ConfigException - * @throws DebugBarException * @throws DiException * @throws ReflectionException */ @@ -79,7 +76,7 @@ function raw_param($value): RawParam /** * Rendered debug bar - * @throws DebugBarException + * @throws DebugBarException|DiException|ReflectionException */ function debugbar(): ?string { diff --git a/tests/Unit/ResourceCache/ViewCacheTest.php b/tests/Unit/ResourceCache/ViewCacheTest.php index ab9760dd..99d56b16 100644 --- a/tests/Unit/ResourceCache/ViewCacheTest.php +++ b/tests/Unit/ResourceCache/ViewCacheTest.php @@ -6,6 +6,7 @@ use Quantum\ResourceCache\ViewCache; use Quantum\Tests\Unit\AppTestCase; use Quantum\Http\Response; +use Quantum\Di\Di; class ViewCacheDouble extends ViewCache { @@ -43,7 +44,7 @@ public function setUp(): void { parent::setUp(); - $this->viewCache = ViewCache::getInstance(); + $this->viewCache = Di::get(ViewCache::class); $this->viewCache->setup(); } @@ -51,6 +52,7 @@ public function tearDown(): void { $this->viewCache->delete($this->route); $this->viewCache->enableCaching(false); + parent::tearDown(); } public function testServeCachedView(): void From ee71a0e41657817a91aeed45761f26af033adb6a Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:34:46 +0400 Subject: [PATCH 37/77] [#382] Migrate ErrorHandler singleton to DI ownership Made-with: Cursor --- src/App/Stages/SetupErrorHandlerStage.php | 3 +- src/Tracer/ErrorHandler.php | 16 ---- tests/Unit/Tracer/ErrorHandlerTest.php | 95 +++++++++++++++++++++++ 3 files changed, 97 insertions(+), 17 deletions(-) create mode 100644 tests/Unit/Tracer/ErrorHandlerTest.php diff --git a/src/App/Stages/SetupErrorHandlerStage.php b/src/App/Stages/SetupErrorHandlerStage.php index b4d441a5..30e2e178 100644 --- a/src/App/Stages/SetupErrorHandlerStage.php +++ b/src/App/Stages/SetupErrorHandlerStage.php @@ -20,6 +20,7 @@ use Quantum\Logger\Factories\LoggerFactory; use Quantum\Tracer\ErrorHandler; use Quantum\App\AppContext; +use Quantum\Di\Di; /** * Class SetupErrorHandlerStage @@ -29,6 +30,6 @@ class SetupErrorHandlerStage implements BootStageInterface { public function process(AppContext $context): void { - ErrorHandler::getInstance()->setup(LoggerFactory::get()); + Di::get(ErrorHandler::class)->setup(LoggerFactory::get()); } } diff --git a/src/Tracer/ErrorHandler.php b/src/Tracer/ErrorHandler.php index 0e29de98..3decf9ec 100644 --- a/src/Tracer/ErrorHandler.php +++ b/src/Tracer/ErrorHandler.php @@ -64,27 +64,11 @@ class ErrorHandler private ?Logger $logger = null; - private static ?ErrorHandler $instance = null; - - private function __construct() - { - // Prevent direct instantiation - } - private function __clone() { // Prevent cloning } - public static function getInstance(): ErrorHandler - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - public function setup(Logger $logger): void { $this->logger = $logger; diff --git a/tests/Unit/Tracer/ErrorHandlerTest.php b/tests/Unit/Tracer/ErrorHandlerTest.php new file mode 100644 index 00000000..deafb505 --- /dev/null +++ b/tests/Unit/Tracer/ErrorHandlerTest.php @@ -0,0 +1,95 @@ +errorHandler = new ErrorHandler(); + } + + public function tearDown(): void + { + restore_error_handler(); + restore_exception_handler(); + parent::tearDown(); + } + + public function testSetupRegistersErrorHandler(): void + { + $logger = Mockery::mock(Logger::class); + + $this->errorHandler->setup($logger); + + $currentHandler = set_error_handler(function () { + }); + restore_error_handler(); + + $this->assertNotNull($currentHandler); + $this->assertIsArray($currentHandler); + $this->assertInstanceOf(ErrorHandler::class, $currentHandler[0]); + $this->assertEquals('handleError', $currentHandler[1]); + } + + public function testSetupRegistersExceptionHandler(): void + { + $logger = Mockery::mock(Logger::class); + + $this->errorHandler->setup($logger); + + $currentHandler = set_exception_handler(function () { + }); + restore_exception_handler(); + + $this->assertNotNull($currentHandler); + $this->assertIsArray($currentHandler); + $this->assertInstanceOf(ErrorHandler::class, $currentHandler[0]); + $this->assertEquals('handleException', $currentHandler[1]); + } + + public function testHandleErrorThrowsErrorException(): void + { + $oldLevel = error_reporting(E_ALL); + + try { + $this->errorHandler->handleError(E_WARNING, 'Test error', __FILE__, __LINE__); + $this->fail('Expected ErrorException was not thrown'); + } catch (ErrorException $e) { + $this->assertEquals('Test error', $e->getMessage()); + $this->assertEquals(E_WARNING, $e->getSeverity()); + } finally { + error_reporting($oldLevel); + } + } + + public function testHandleErrorReturnsFalseForSuppressedErrors(): void + { + $oldLevel = error_reporting(0); + + try { + $result = $this->errorHandler->handleError(E_NOTICE, 'Suppressed', __FILE__, __LINE__); + $this->assertFalse($result); + } finally { + error_reporting($oldLevel); + } + } + + public function testErrorTypesConstant(): void + { + $this->assertEquals('error', ErrorHandler::ERROR_TYPES[E_ERROR]); + $this->assertEquals('warning', ErrorHandler::ERROR_TYPES[E_WARNING]); + $this->assertEquals('notice', ErrorHandler::ERROR_TYPES[E_NOTICE]); + $this->assertEquals('error', ErrorHandler::ERROR_TYPES[E_PARSE]); + } +} From 3b2ee519bc9227fa46eccbbd4b85cf6e82148664 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:46:09 +0400 Subject: [PATCH 38/77] [#382] Migrate HookManager singleton to DI ownership Made-with: Cursor --- src/App/Adapters/WebAppAdapter.php | 3 +-- src/Hook/Helpers/hook.php | 3 ++- src/Hook/HookManager.php | 32 ++++++++--------------------- tests/Unit/Hook/HookManagerTest.php | 18 +++------------- 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index d954082c..42f40d72 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -44,7 +44,6 @@ use Quantum\Debugger\Debugger; use Quantum\App\Enums\AppType; use Quantum\App\BootPipeline; -use Quantum\Hook\HookManager; use Quantum\Http\Response; use Quantum\Http\Request; use ReflectionException; @@ -143,7 +142,7 @@ public function start(): ?int $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { - $debugger->addToStoreCell(Debugger::HOOKS, 'info', HookManager::getInstance()->getRegistered()); + $debugger->addToStoreCell(Debugger::HOOKS, 'info', hook()->getRegistered()); } $middlewareManager = new MiddlewareManager($matchedRoute); diff --git a/src/Hook/Helpers/hook.php b/src/Hook/Helpers/hook.php index 3a4d1b11..4414311c 100644 --- a/src/Hook/Helpers/hook.php +++ b/src/Hook/Helpers/hook.php @@ -13,11 +13,12 @@ */ use Quantum\Hook\HookManager; +use Quantum\Di\Di; /** * Gets the HookManager instance */ function hook(): HookManager { - return HookManager::getInstance(); + return Di::get(HookManager::class); } diff --git a/src/Hook/HookManager.php b/src/Hook/HookManager.php index 0a159c1e..fbf8b2b1 100644 --- a/src/Hook/HookManager.php +++ b/src/Hook/HookManager.php @@ -38,9 +38,7 @@ class HookManager * Registered hooks store * @var array> */ - private static array $store = []; - - private static ?HookManager $instance = null; + private array $store = []; /** * @throws HookException @@ -48,7 +46,7 @@ class HookManager * @throws DiException * @throws ReflectionException|LoaderException */ - private function __construct() + public function __construct() { if (!config()->has('hooks')) { config()->import(new Setup('config', 'hooks')); @@ -61,18 +59,6 @@ private function __construct() } } - /** - * HookManager instance - */ - public static function getInstance(): HookManager - { - if (self::$instance == null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * Adds a new listener for a given hook * @throws HookException @@ -83,7 +69,7 @@ public function on(string $name, callable $function): void throw HookException::unregisteredHookName($name); } - self::$store[$name][] = $function; + $this->store[$name][] = $function; } /** @@ -97,8 +83,8 @@ public function fire(string $name, ?array $args = null): void throw HookException::unregisteredHookName($name); } - foreach (self::$store[$name] as $index => $fn) { - unset(self::$store[$name][$index]); + foreach ($this->store[$name] as $index => $fn) { + unset($this->store[$name][$index]); $fn($args); } } @@ -107,9 +93,9 @@ public function fire(string $name, ?array $args = null): void * Gets all registered hooks * @return array> */ - public static function getRegistered(): array + public function getRegistered(): array { - return self::$store; + return $this->store; } /** @@ -123,7 +109,7 @@ protected function register(string $name): void throw HookException::hookDuplicateName($name); } - self::$store[$name] = []; + $this->store[$name] = []; } /** @@ -131,6 +117,6 @@ protected function register(string $name): void */ protected function exists(string $name): bool { - return array_key_exists($name, self::$store); + return array_key_exists($name, $this->store); } } diff --git a/tests/Unit/Hook/HookManagerTest.php b/tests/Unit/Hook/HookManagerTest.php index 176665dc..e9c9b948 100644 --- a/tests/Unit/Hook/HookManagerTest.php +++ b/tests/Unit/Hook/HookManagerTest.php @@ -8,21 +8,9 @@ class HookManagerTest extends AppTestCase { - public function setUp(): void + public function testHookHelperReturnsInstance(): void { - parent::setUp(); - - $this->setPrivateProperty(HookManager::class, 'instance', null); - $this->setPrivateProperty(HookManager::class, 'store', []); - } - - public function testGetInstanceReturnsSameObject(): void - { - $instance1 = HookManager::getInstance(); - $instance2 = HookManager::getInstance(); - - $this->assertInstanceOf(HookManager::class, $instance1); - $this->assertSame($instance1, $instance2); + $this->assertInstanceOf(HookManager::class, hook()); } public function testOnAndFireOutputsCorrectly(): void @@ -102,7 +90,7 @@ public function testGetRegisteredReturnsHookStore(): void hook()->on('SAVE', function (): void { }); - $store = HookManager::getRegistered(); + $store = hook()->getRegistered(); $this->assertArrayHasKey('SAVE', $store); $this->assertCount(1, $store['SAVE']); From 8894b989ba093530a50925a4eafc30b51e0537df Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:17:38 +0400 Subject: [PATCH 39/77] [#382] Replace fs()->exists() with file_exists() in Environment::load() Made-with: Cursor --- src/Environment/Environment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index ce4afe5c..074c6cee 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -94,7 +94,7 @@ public function load(Setup $setup): void $this->envFile = '.env' . ($appEnv !== Env::PRODUCTION ? ".$appEnv" : ''); - if (!fs()->exists($this->getEnvFilePath())) { + if (!file_exists($this->getEnvFilePath())) { throw EnvException::fileNotFound($this->envFile); } From 6ab41c5417272d6c543e482459ce04031703d011 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:34:29 +0400 Subject: [PATCH 40/77] [#382] Migrate Cookie singleton to DI ownership Made-with: Cursor --- src/Cookie/Cookie.php | 40 ++++++++------------------------ src/Cookie/Helpers/cookie.php | 7 +++++- tests/Unit/Cookie/CookieTest.php | 3 ++- 3 files changed, 18 insertions(+), 32 deletions(-) diff --git a/src/Cookie/Cookie.php b/src/Cookie/Cookie.php index 174d1e21..bc45c1a3 100644 --- a/src/Cookie/Cookie.php +++ b/src/Cookie/Cookie.php @@ -29,34 +29,14 @@ class Cookie implements CookieStorageInterface * Cookie storage * @var array */ - private static array $storage = []; + private array $storage; /** - * Cookie instance - */ - private static ?Cookie $instance = null; - - /** - * Cookie constructor. - */ - private function __construct() - { - // Preventing to create a new object through constructor - } - - /** - * Gets the cookie instance * @param array $storage */ - public static function getInstance(array &$storage): Cookie + public function __construct(array &$storage) { - self::$storage = &$storage; - - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; + $this->storage = &$storage; } /** @@ -68,7 +48,7 @@ public function all(): array { $allCookies = []; - foreach (self::$storage as $key => $value) { + foreach ($this->storage as $key => $value) { $allCookies[$key] = crypto_decode($value); } @@ -80,7 +60,7 @@ public function all(): array */ public function has(string $key): bool { - return isset(self::$storage[$key]) && !empty(self::$storage[$key]); + return isset($this->storage[$key]) && !empty($this->storage[$key]); } /** @@ -89,7 +69,7 @@ public function has(string $key): bool */ public function get(string $key) { - return $this->has($key) ? crypto_decode(self::$storage[$key]) : null; + return $this->has($key) ? crypto_decode($this->storage[$key]) : null; } /** @@ -98,7 +78,7 @@ public function get(string $key) */ public function set(string $key, $value = '', int $time = 0, string $path = '/', string $domain = '', bool $secure = false, bool $httponly = false): void { - self::$storage[$key] = crypto_encode($value); + $this->storage[$key] = crypto_encode($value); setcookie($key, crypto_encode($value), ['expires' => $time !== 0 ? time() + $time : $time, 'path' => $path, 'domain' => $domain, 'secure' => $secure, 'httponly' => $httponly]); } @@ -108,7 +88,7 @@ public function set(string $key, $value = '', int $time = 0, string $path = '/', public function delete(string $key, string $path = '/'): void { if ($this->has($key)) { - unset(self::$storage[$key]); + unset($this->storage[$key]); setcookie($key, '', ['expires' => time() - 3600, 'path' => $path]); } } @@ -118,8 +98,8 @@ public function delete(string $key, string $path = '/'): void */ public function flush(): void { - if (count(self::$storage)) { - foreach (array_keys(self::$storage) as $key) { + if (count($this->storage)) { + foreach (array_keys($this->storage) as $key) { $this->delete($key, '/'); } } diff --git a/src/Cookie/Helpers/cookie.php b/src/Cookie/Helpers/cookie.php index 5ab4748d..c22db8c5 100644 --- a/src/Cookie/Helpers/cookie.php +++ b/src/Cookie/Helpers/cookie.php @@ -13,11 +13,16 @@ */ use Quantum\Cookie\Cookie; +use Quantum\Di\Di; /** * Gets cookie handler */ function cookie(): Cookie { - return Cookie::getInstance($_COOKIE); + if (!Di::has(Cookie::class)) { + Di::set(Cookie::class, new Cookie($_COOKIE)); + } + + return Di::get(Cookie::class); } diff --git a/tests/Unit/Cookie/CookieTest.php b/tests/Unit/Cookie/CookieTest.php index 192e0290..de36723b 100644 --- a/tests/Unit/Cookie/CookieTest.php +++ b/tests/Unit/Cookie/CookieTest.php @@ -15,12 +15,13 @@ public function setUp(): void { parent::setUp(); - $this->cookie = Cookie::getInstance($this->storage); + $this->cookie = new Cookie($this->storage); } public function tearDown(): void { $this->cookie->flush(); + parent::tearDown(); } public function testCookieConstructor(): void From fc883f05478b019d2222faee8f3a5c3ac8e13da6 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:49:39 +0400 Subject: [PATCH 41/77] [#382] Migrate ModuleLoader singleton to DI ownership Made-with: Cursor --- src/App/Adapters/WebAppAdapter.php | 2 +- src/Console/Commands/OpenApiCommand.php | 2 +- src/Console/Commands/RouteListCommand.php | 2 +- src/Module/ModuleLoader.php | 49 +++++++++-------------- src/Module/ModuleManager.php | 3 +- tests/Unit/Module/ModuleLoaderTest.php | 5 +-- 6 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 42f40d72..57cf17f7 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -116,7 +116,7 @@ public function start(): ?int (new SetupErrorHandlerStage())->process($this->context); $this->initializeDebugger(); - $moduleLoader = ModuleLoader::getInstance(); + $moduleLoader = Di::get(ModuleLoader::class); $builder = new RouteBuilder(); diff --git a/src/Console/Commands/OpenApiCommand.php b/src/Console/Commands/OpenApiCommand.php index 5df57383..1785fcc3 100644 --- a/src/Console/Commands/OpenApiCommand.php +++ b/src/Console/Commands/OpenApiCommand.php @@ -97,7 +97,7 @@ public function __construct() */ public function exec(): void { - $moduleLoader = ModuleLoader::getInstance(); + $moduleLoader = Di::get(ModuleLoader::class); $builder = new RouteBuilder(); diff --git a/src/Console/Commands/RouteListCommand.php b/src/Console/Commands/RouteListCommand.php index a1db75ec..c204c164 100644 --- a/src/Console/Commands/RouteListCommand.php +++ b/src/Console/Commands/RouteListCommand.php @@ -58,7 +58,7 @@ class RouteListCommand extends QtCommand public function exec(): void { try { - $moduleLoader = ModuleLoader::getInstance(); + $moduleLoader = Di::get(ModuleLoader::class); $builder = new RouteBuilder(); $routeCollection = $builder->build( diff --git a/src/Module/ModuleLoader.php b/src/Module/ModuleLoader.php index 72be6b71..86978959 100644 --- a/src/Module/ModuleLoader.php +++ b/src/Module/ModuleLoader.php @@ -37,41 +37,30 @@ class ModuleLoader /** * @var array> */ - private static array $moduleDependencies = []; + private array $moduleDependencies = []; /** * @var array> */ - private static array $moduleConfigs = []; + private array $moduleConfigs = []; /** @var array */ - private static array $moduleRouteClosures = []; + private array $moduleRouteClosures = []; private FileSystem $fs; - private static ?ModuleLoader $instance = null; - /** * @throws BaseException * @throws DiException * @throws ConfigException * @throws ReflectionException|ModuleException */ - private function __construct() + public function __construct() { $this->fs = FileSystemFactory::get(); Di::registerDependencies($this->loadModulesDependencies()); } - public static function getInstance(): ModuleLoader - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } - /** * @return array * @throws ModuleException @@ -79,13 +68,13 @@ public static function getInstance(): ModuleLoader */ public function loadModulesRoutes(): array { - if (empty(self::$moduleConfigs)) { + if (empty($this->moduleConfigs)) { $this->loadModuleConfig(); } $modulesRoutes = []; - foreach (self::$moduleConfigs as $module => $options) { + foreach ($this->moduleConfigs as $module => $options) { if (!$this->isModuleEnabled($options)) { continue; } @@ -102,8 +91,8 @@ public function loadModulesRoutes(): array */ private function getModuleRouteDefinitions(string $module): Closure { - if (isset(self::$moduleRouteClosures[$module])) { - return self::$moduleRouteClosures[$module]; + if (isset($this->moduleRouteClosures[$module])) { + return $this->moduleRouteClosures[$module]; } $moduleRoutesPath = modules_dir() . DS . $module . DS . 'routes' . DS . 'routes.php'; @@ -112,13 +101,13 @@ private function getModuleRouteDefinitions(string $module): Closure throw ModuleException::moduleRoutesNotFound($module); } - $closure = $this->fs->require($moduleRoutesPath, true); + $closure = $this->fs->require($moduleRoutesPath); if (!$closure instanceof Closure) { throw RouteException::notClosure(); } - return self::$moduleRouteClosures[$module] = $closure; + return $this->moduleRouteClosures[$module] = $closure; } /** @@ -127,13 +116,13 @@ private function getModuleRouteDefinitions(string $module): Closure */ public function loadModulesDependencies(): array { - if (empty(self::$moduleConfigs)) { + if (empty($this->moduleConfigs)) { $this->loadModuleConfig(); } $modulesDependencies = []; - foreach (self::$moduleConfigs as $module => $options) { + foreach ($this->moduleConfigs as $module => $options) { $modulesDependencies = array_merge($modulesDependencies, $this->getModuleDependencies($module)); } @@ -145,19 +134,19 @@ public function loadModulesDependencies(): array */ public function getModuleDependencies(string $module): array { - if (!isset(self::$moduleDependencies[$module])) { + if (!isset($this->moduleDependencies[$module])) { $file = modules_dir() . DS . $module . DS . 'config' . DS . 'dependencies.php'; if ($this->fs->exists($file)) { $deps = $this->fs->require($file); - self::$moduleDependencies[$module] = is_array($deps) ? $deps : []; + $this->moduleDependencies[$module] = is_array($deps) ? $deps : []; } else { - self::$moduleDependencies[$module] = []; + $this->moduleDependencies[$module] = []; } } - return self::$moduleDependencies[$module]; + return $this->moduleDependencies[$module]; } /** @@ -171,7 +160,7 @@ private function loadModuleConfig(): void throw ModuleException::moduleConfigNotFound(); } - self::$moduleConfigs = $this->fs->require($configPath); + $this->moduleConfigs = $this->fs->require($configPath); } /** @@ -180,11 +169,11 @@ private function loadModuleConfig(): void */ public function getModuleConfigs(): array { - if (empty(self::$moduleConfigs)) { + if (empty($this->moduleConfigs)) { $this->loadModuleConfig(); } - return self::$moduleConfigs; + return $this->moduleConfigs; } /** diff --git a/src/Module/ModuleManager.php b/src/Module/ModuleManager.php index 435c7da8..a76b8603 100644 --- a/src/Module/ModuleManager.php +++ b/src/Module/ModuleManager.php @@ -24,6 +24,7 @@ use Quantum\Di\Exceptions\DiException; use Quantum\Storage\FileSystem; use ReflectionException; +use Quantum\Di\Di; use Exception; /** @@ -90,7 +91,7 @@ public function addModuleConfig(): void throw ModuleException::missingModuleDirectory(); } - $moduleConfigs = ModuleLoader::getInstance()->getModuleConfigs(); + $moduleConfigs = Di::get(ModuleLoader::class)->getModuleConfigs(); foreach ($moduleConfigs as $module => $options) { if ($module == $this->moduleName || $options['prefix'] == strtolower($this->moduleName)) { diff --git a/tests/Unit/Module/ModuleLoaderTest.php b/tests/Unit/Module/ModuleLoaderTest.php index ab5161f5..fe3ed8c2 100644 --- a/tests/Unit/Module/ModuleLoaderTest.php +++ b/tests/Unit/Module/ModuleLoaderTest.php @@ -4,6 +4,7 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Module\ModuleLoader; +use Quantum\Di\Di; use Closure; class ModuleLoaderTest extends AppTestCase @@ -13,9 +14,7 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(ModuleLoader::class, 'instance', null); - - $this->moduleLoader = ModuleLoader::getInstance(); + $this->moduleLoader = Di::get(ModuleLoader::class); } public function tearDown(): void From bd518fbf0c7e3b6c81c70a4b9f3c4e1417d0d6bb Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:21:55 +0400 Subject: [PATCH 42/77] [#373] Clean up docblocks, add missing @throws annotations, and modernize property types Made-with: Cursor --- coverage.xml | 4904 ----------------- src/App/Adapters/ConsoleAppAdapter.php | 6 +- src/App/AppContext.php | 3 +- src/App/Exceptions/BaseException.php | 45 +- src/App/Stages/LoadAppConfigStage.php | 7 + src/App/Stages/LoadEnvironmentStage.php | 7 + src/App/Stages/LoadHelpersStage.php | 5 + src/App/Stages/LoadLanguageStage.php | 8 + src/App/Stages/SetupErrorHandlerStage.php | 7 + src/Archive/Factories/ArchiveFactory.php | 3 +- src/Asset/Asset.php | 5 + src/Asset/AssetManager.php | 5 +- src/Asset/Helpers/asset.php | 2 +- src/Auth/Adapters/SessionAuthAdapter.php | 1 + src/Auth/Factories/AuthFactory.php | 19 +- src/Auth/Helpers/auth.php | 7 +- src/Auth/User.php | 1 - src/Cache/Adapters/DatabaseAdapter.php | 33 +- src/Cache/Factories/CacheFactory.php | 12 +- src/Cache/Helpers/cache.php | 5 +- src/Cache/Traits/CacheTrait.php | 10 +- src/Captcha/Adapters/HcaptchaAdapter.php | 5 +- src/Captcha/Adapters/RecaptchaAdapter.php | 5 +- src/Captcha/Factories/CaptchaFactory.php | 7 +- src/Captcha/Helpers/captcha.php | 5 +- src/Captcha/Traits/CaptchaTrait.php | 10 +- src/Config/Helpers/config.php | 2 + src/Console/Commands/CronRunCommand.php | 10 +- .../Commands/InstallToolkitCommand.php | 7 +- .../Commands/MigrationGenerateCommand.php | 2 - .../Commands/MigrationMigrateCommand.php | 9 +- src/Console/Commands/OpenApiCommand.php | 6 +- .../Commands/ResourceCacheClearCommand.php | 7 +- src/Console/Commands/RouteListCommand.php | 3 +- src/Cookie/Helpers/cookie.php | 2 + src/Cron/CronLock.php | 29 +- src/Cron/CronManager.php | 19 +- src/Cron/CronTask.php | 4 +- src/Cron/Helpers/cron.php | 9 +- src/Database/Adapters/Idiorm/IdiormDbal.php | 3 + src/Database/Adapters/Idiorm/IdiormPatch.php | 2 +- .../Adapters/Idiorm/Statements/Criteria.php | 15 +- .../Adapters/Idiorm/Statements/Join.php | 3 +- .../Adapters/Idiorm/Statements/Model.php | 3 +- .../Adapters/Idiorm/Statements/Query.php | 13 +- src/Database/Adapters/Sleekdb/SleekDbal.php | 2 +- .../Adapters/Sleekdb/Statements/Model.php | 4 +- src/Database/Factories/TableFactory.php | 3 +- src/Database/Traits/RelationalTrait.php | 15 +- src/Debugger/Debugger.php | 3 + src/Di/Di.php | 5 +- .../Adapters/AsymmetricEncryptionAdapter.php | 10 +- .../Adapters/SymmetricEncryptionAdapter.php | 10 +- src/Encryption/Factories/CryptorFactory.php | 3 +- src/Encryption/Helpers/encryption.php | 4 +- src/Environment/Environment.php | 11 +- src/Environment/Helpers/server.php | 5 + src/Hook/Helpers/hook.php | 2 + src/Hook/HookManager.php | 6 +- src/Http/Helpers/http.php | 17 +- src/Http/Request/HttpRequest.php | 6 +- src/Http/Response/HttpResponse.php | 4 +- src/Http/Traits/Request/Internal.php | 5 +- src/Http/Traits/Request/Params.php | 5 +- src/Http/Traits/Request/RawInput.php | 15 +- src/Http/Traits/Response/Body.php | 1 - src/HttpClient/HttpClient.php | 2 +- src/Jwt/JwtToken.php | 3 +- src/Lang/Factories/LangFactory.php | 19 +- src/Lang/Helpers/lang.php | 17 +- src/Lang/Lang.php | 8 +- src/Lang/Translator.php | 12 +- src/Logger/Adapters/DailyAdapter.php | 6 +- src/Logger/Adapters/MessageAdapter.php | 4 +- src/Logger/Adapters/SingleAdapter.php | 8 +- src/Logger/Contracts/ReportableInterface.php | 2 - src/Logger/Factories/LoggerFactory.php | 13 +- src/Logger/Helpers/logger.php | 35 +- src/Logger/Traits/LoggerTrait.php | 11 +- src/Mailer/Adapters/MailgunAdapter.php | 5 +- src/Mailer/Adapters/MandrillAdapter.php | 5 +- src/Mailer/Adapters/ResendAdapter.php | 5 +- src/Mailer/Adapters/SendgridAdapter.php | 5 +- src/Mailer/Adapters/SendinblueAdapter.php | 5 +- src/Mailer/Adapters/SmtpAdapter.php | 3 + src/Mailer/Factories/MailerFactory.php | 12 +- src/Middleware/MiddlewareManager.php | 5 +- src/Migration/MigrationManager.php | 16 +- src/Model/DbModel.php | 2 +- src/Model/Factories/ModelFactory.php | 17 +- src/Model/Helpers/model.php | 5 +- src/Model/Traits/SoftDeletes.php | 4 +- src/Module/ModuleLoader.php | 11 +- src/Module/ModuleManager.php | 9 +- src/Paginator/Adapters/ArrayPaginator.php | 3 + src/Paginator/Adapters/ModelPaginator.php | 5 + src/Paginator/Traits/PaginatorTrait.php | 11 +- src/Renderer/Adapters/HtmlAdapter.php | 5 +- src/Renderer/Adapters/TwigAdapter.php | 9 +- src/Renderer/Factories/RendererFactory.php | 2 +- src/ResourceCache/ViewCache.php | 10 +- .../Database/DatabaseSessionAdapter.php | 9 +- src/Session/Factories/SessionFactory.php | 12 +- src/Session/Helpers/session.php | 5 +- src/Session/Traits/SessionTrait.php | 6 +- .../Contracts/TokenServiceInterface.php | 1 - src/Storage/Factories/FileSystemFactory.php | 25 +- src/Storage/Helpers/fs.php | 5 +- src/Storage/Traits/CloudAppTrait.php | 4 +- src/Storage/UploadedFile.php | 28 +- src/Tracer/ErrorHandler.php | 23 +- src/Validation/Traits/General.php | 11 +- src/Validation/Validator.php | 6 +- src/View/Helpers/view.php | 11 +- src/View/QtView.php | 3 +- 115 files changed, 364 insertions(+), 5490 deletions(-) delete mode 100644 coverage.xml diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 835c4570..00000000 --- a/coverage.xml +++ /dev/null @@ -1,4904 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index 3ad4d469..10fb21c0 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -28,7 +28,7 @@ use Quantum\App\Traits\ConsoleAppTrait; use Quantum\App\Enums\AppType; use Quantum\App\BootPipeline; -use ReflectionException; +use Exception; if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); @@ -76,8 +76,8 @@ public function __construct() } /** - * @throws ReflectionException - */ + * @throws Exception + */ public function start(): ?int { try { diff --git a/src/App/AppContext.php b/src/App/AppContext.php index 803a3e38..ec513d39 100644 --- a/src/App/AppContext.php +++ b/src/App/AppContext.php @@ -17,6 +17,7 @@ namespace Quantum\App; use Quantum\App\Enums\AppType; +use InvalidArgumentException; /** * Class AppContext @@ -29,7 +30,7 @@ class AppContext public function __construct(string $mode) { if (!in_array($mode, [AppType::WEB, AppType::CONSOLE], true)) { - throw new \InvalidArgumentException("Invalid app mode: $mode"); + throw new InvalidArgumentException("Invalid app mode: $mode"); } $this->mode = $mode; diff --git a/src/App/Exceptions/BaseException.php b/src/App/Exceptions/BaseException.php index ff55e5ea..42a14d1d 100644 --- a/src/App/Exceptions/BaseException.php +++ b/src/App/Exceptions/BaseException.php @@ -30,10 +30,7 @@ final public function __construct(string $message = '', int $code = 0) parent::__construct($message, $code); } - /** - * @return static - */ - public static function methodNotSupported(string $methodName, string $className) + public static function methodNotSupported(string $methodName, string $className): BaseException { return new static( _message(ExceptionMessages::METHOD_NOT_SUPPORTED, [$methodName, $className]), @@ -41,10 +38,7 @@ public static function methodNotSupported(string $methodName, string $className) ); } - /** - * @return static - */ - public static function adapterNotSupported(string $name) + public static function adapterNotSupported(string $name): BaseException { return new static( _message(ExceptionMessages::ADAPTER_NOT_SUPPORTED, [$name]), @@ -52,10 +46,7 @@ public static function adapterNotSupported(string $name) ); } - /** - * @return static - */ - public static function driverNotSupported(string $name) + public static function driverNotSupported(string $name): BaseException { return new static( _message(ExceptionMessages::DRIVER_NOT_SUPPORTED, [$name]), @@ -63,10 +54,7 @@ public static function driverNotSupported(string $name) ); } - /** - * @return static - */ - public static function fileNotFound(string $name) + public static function fileNotFound(string $name): BaseException { return new static( _message(ExceptionMessages::FILE_NOT_FOUND, [$name]), @@ -74,10 +62,7 @@ public static function fileNotFound(string $name) ); } - /** - * @return static - */ - public static function notFound(string $subject, string $name) + public static function notFound(string $subject, string $name): BaseException { return new static( _message(ExceptionMessages::NOT_FOUND, [$subject, $name]), @@ -85,10 +70,7 @@ public static function notFound(string $subject, string $name) ); } - /** - * @return static - */ - public static function notInstanceOf(string $instance, string $name) + public static function notInstanceOf(string $instance, string $name): BaseException { return new static( _message(ExceptionMessages::NOT_INSTANCE_OF, [$instance, $name]), @@ -96,10 +78,7 @@ public static function notInstanceOf(string $instance, string $name) ); } - /** - * @return static - */ - public static function cantConnect(string $name) + public static function cantConnect(string $name): BaseException { return new static( _message(ExceptionMessages::CANT_CONNECT, [$name]), @@ -107,10 +86,7 @@ public static function cantConnect(string $name) ); } - /** - * @return static - */ - public static function missingConfig(string $name) + public static function missingConfig(string $name): BaseException { return new static( _message(ExceptionMessages::MISSING_CONFIG, $name), @@ -118,10 +94,7 @@ public static function missingConfig(string $name) ); } - /** - * @return static - */ - public static function requestMethodNotAvailable(string $name) + public static function requestMethodNotAvailable(string $name): BaseException { return new static( _message(ExceptionMessages::UNAVAILABLE_REQUEST_METHOD, [$name]), diff --git a/src/App/Stages/LoadAppConfigStage.php b/src/App/Stages/LoadAppConfigStage.php index a23c6612..49e90b91 100644 --- a/src/App/Stages/LoadAppConfigStage.php +++ b/src/App/Stages/LoadAppConfigStage.php @@ -17,8 +17,12 @@ namespace Quantum\App\Stages; use Quantum\App\Contracts\BootStageInterface; +use Quantum\Config\Exceptions\ConfigException; +use Quantum\Loader\Exceptions\LoaderException; +use Quantum\Di\Exceptions\DiException; use Quantum\App\AppContext; use Quantum\Loader\Setup; +use ReflectionException; /** * Class LoadAppConfigStage @@ -26,6 +30,9 @@ */ class LoadAppConfigStage implements BootStageInterface { + /** + * @throws LoaderException|ConfigException|DiException|ReflectionException + */ public function process(AppContext $context): void { if (!config()->has('app')) { diff --git a/src/App/Stages/LoadEnvironmentStage.php b/src/App/Stages/LoadEnvironmentStage.php index 97975f58..14c0958d 100644 --- a/src/App/Stages/LoadEnvironmentStage.php +++ b/src/App/Stages/LoadEnvironmentStage.php @@ -16,10 +16,14 @@ namespace Quantum\App\Stages; +use Quantum\Environment\Exceptions\EnvException; use Quantum\App\Contracts\BootStageInterface; +use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Environment\Environment; use Quantum\App\AppContext; use Quantum\Loader\Setup; +use ReflectionException; /** * Class LoadEnvironmentStage @@ -27,6 +31,9 @@ */ class LoadEnvironmentStage implements BootStageInterface { + /** + * @throws EnvException|DiException|BaseException|ReflectionException + */ public function process(AppContext $context): void { $environment = Environment::getInstance(); diff --git a/src/App/Stages/LoadHelpersStage.php b/src/App/Stages/LoadHelpersStage.php index 5007e780..8bf70697 100644 --- a/src/App/Stages/LoadHelpersStage.php +++ b/src/App/Stages/LoadHelpersStage.php @@ -17,8 +17,10 @@ namespace Quantum\App\Stages; use Quantum\App\Contracts\BootStageInterface; +use Quantum\Di\Exceptions\DiException; use Quantum\App\AppContext; use Quantum\Loader\Loader; +use ReflectionException; use Quantum\App\App; use Quantum\Di\Di; @@ -28,6 +30,9 @@ */ class LoadHelpersStage implements BootStageInterface { + /** + * @throws DiException|ReflectionException + */ public function process(AppContext $context): void { $loader = Di::get(Loader::class); diff --git a/src/App/Stages/LoadLanguageStage.php b/src/App/Stages/LoadLanguageStage.php index c0568857..430d2b8e 100644 --- a/src/App/Stages/LoadLanguageStage.php +++ b/src/App/Stages/LoadLanguageStage.php @@ -16,9 +16,14 @@ namespace Quantum\App\Stages; +use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Contracts\BootStageInterface; +use Quantum\Lang\Exceptions\LangException; +use Quantum\App\Exceptions\BaseException; use Quantum\Lang\Factories\LangFactory; +use Quantum\Di\Exceptions\DiException; use Quantum\App\AppContext; +use ReflectionException; /** * Class LoadLanguageStage @@ -26,6 +31,9 @@ */ class LoadLanguageStage implements BootStageInterface { + /** + * @throws LangException|ConfigException|DiException|BaseException|ReflectionException + */ public function process(AppContext $context): void { $lang = LangFactory::get(); diff --git a/src/App/Stages/SetupErrorHandlerStage.php b/src/App/Stages/SetupErrorHandlerStage.php index 30e2e178..4fc877fb 100644 --- a/src/App/Stages/SetupErrorHandlerStage.php +++ b/src/App/Stages/SetupErrorHandlerStage.php @@ -16,10 +16,14 @@ namespace Quantum\App\Stages; +use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Contracts\BootStageInterface; use Quantum\Logger\Factories\LoggerFactory; +use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Tracer\ErrorHandler; use Quantum\App\AppContext; +use ReflectionException; use Quantum\Di\Di; /** @@ -28,6 +32,9 @@ */ class SetupErrorHandlerStage implements BootStageInterface { + /** + * @throws ConfigException|DiException|BaseException|ReflectionException + */ public function process(AppContext $context): void { Di::get(ErrorHandler::class)->setup(LoggerFactory::get()); diff --git a/src/Archive/Factories/ArchiveFactory.php b/src/Archive/Factories/ArchiveFactory.php index da8c7eb7..a27b8e89 100644 --- a/src/Archive/Factories/ArchiveFactory.php +++ b/src/Archive/Factories/ArchiveFactory.php @@ -22,6 +22,7 @@ use Quantum\Archive\Adapters\ZipAdapter; use Quantum\Archive\Enums\ArchiveType; use Quantum\Archive\Archive; +use ReflectionException; use Quantum\Di\Di; /** @@ -41,7 +42,7 @@ class ArchiveFactory private array $instances = []; /** - * @throws BaseException + * @throws BaseException|ReflectionException */ public static function get(string $type = ArchiveType::PHAR): Archive { diff --git a/src/Asset/Asset.php b/src/Asset/Asset.php index 64ab140a..6baec576 100644 --- a/src/Asset/Asset.php +++ b/src/Asset/Asset.php @@ -16,6 +16,9 @@ namespace Quantum\Asset; +use Quantum\Di\Exceptions\DiException; +use ReflectionException; + /** * Class Asset * @package Quantum\Asset @@ -110,6 +113,7 @@ public function getAttributes(): ?array /** * Gets asset url + * @throws DiException|ReflectionException */ public function url(): string { @@ -122,6 +126,7 @@ public function url(): string /** * Renders asset tag + * @throws DiException|ReflectionException */ public function tag(): string { diff --git a/src/Asset/AssetManager.php b/src/Asset/AssetManager.php index f580c956..aa7aee13 100644 --- a/src/Asset/AssetManager.php +++ b/src/Asset/AssetManager.php @@ -17,6 +17,8 @@ namespace Quantum\Asset; use Quantum\Asset\Exceptions\AssetException; +use Quantum\Di\Exceptions\DiException; +use ReflectionException; /** * Class AssetFactory @@ -67,6 +69,7 @@ public function get(string $name): ?Asset /** * Asset url + * @throws DiException|ReflectionException */ public function url(string $path): string { @@ -110,7 +113,7 @@ public function flush(): void /** * Dumps the assets - * @throws AssetException + * @throws AssetException|DiException|ReflectionException */ public function dump(int $type): void { diff --git a/src/Asset/Helpers/asset.php b/src/Asset/Helpers/asset.php index 07fa47bd..db3cc6aa 100644 --- a/src/Asset/Helpers/asset.php +++ b/src/Asset/Helpers/asset.php @@ -13,13 +13,13 @@ */ use Quantum\Asset\Exceptions\AssetException; -use Quantum\Lang\Exceptions\LangException; use Quantum\Di\Exceptions\DiException; use Quantum\Asset\AssetManager; use Quantum\Di\Di; /** * Gets the AssetManager instance + * @throws DiException|ReflectionException */ function asset(): AssetManager { diff --git a/src/Auth/Adapters/SessionAuthAdapter.php b/src/Auth/Adapters/SessionAuthAdapter.php index 3e051a15..31ccf5e1 100644 --- a/src/Auth/Adapters/SessionAuthAdapter.php +++ b/src/Auth/Adapters/SessionAuthAdapter.php @@ -172,6 +172,7 @@ public function verifyOtp(int $otp, string $otpToken): bool /** * Check Remember Token * @return User|false + * @throws BaseException */ private function checkRememberToken() { diff --git a/src/Auth/Factories/AuthFactory.php b/src/Auth/Factories/AuthFactory.php index a1f9f663..00f6c016 100644 --- a/src/Auth/Factories/AuthFactory.php +++ b/src/Auth/Factories/AuthFactory.php @@ -65,12 +65,7 @@ public static function get(?string $adapter = null): Auth } /** - * @throws AuthException - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws AuthException|ConfigException|ServiceException|BaseException|DiException|ReflectionException */ public function resolve(?string $adapter = null): Auth { @@ -90,12 +85,7 @@ public function resolve(?string $adapter = null): Auth } /** - * @throws AuthException - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws AuthException|ConfigException|ServiceException|BaseException|DiException|ReflectionException */ private function createInstance(string $adapterClass, string $adapter): Auth { @@ -126,10 +116,7 @@ private function getAdapterClass(string $adapter): string } /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws ServiceException|BaseException|DiException|ReflectionException */ private function createAuthService(string $adapter): AuthServiceInterface { diff --git a/src/Auth/Helpers/auth.php b/src/Auth/Helpers/auth.php index e4f1e7bd..cd75ee36 100644 --- a/src/Auth/Helpers/auth.php +++ b/src/Auth/Helpers/auth.php @@ -22,12 +22,7 @@ /** * Gets the Auth handler - * @throws AuthException - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws AuthException|ConfigException|ServiceException|BaseException|DiException|ReflectionException */ function auth(?string $adapter = null): Auth { diff --git a/src/Auth/User.php b/src/Auth/User.php index 1bbac695..17f2cde7 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -106,5 +106,4 @@ public function __get(string $property) { return $this->getFieldValue($property); } - } diff --git a/src/Cache/Adapters/DatabaseAdapter.php b/src/Cache/Adapters/DatabaseAdapter.php index 555fb002..902fb301 100644 --- a/src/Cache/Adapters/DatabaseAdapter.php +++ b/src/Cache/Adapters/DatabaseAdapter.php @@ -16,6 +16,7 @@ namespace Quantum\Cache\Adapters; +use Quantum\Model\Exceptions\ModelException; use Quantum\Cache\Enums\ExceptionMessages; use Quantum\App\Exceptions\BaseException; use Quantum\Model\Factories\ModelFactory; @@ -155,22 +156,8 @@ public function setMultiple($values, $ttl = null): bool /** * @inheritDoc - */ - public function delete($key): bool - { - $cacheItem = $this->cacheModel->findOneBy('key', $this->keyHash($key)); - - if ($cacheItem !== null && !empty($cacheItem->asArray())) { - return $cacheItem->delete(); - } - - return false; - } - - /** - * @inheritDoc - * @throws InvalidArgumentException * @param iterable $keys + * @throws ModelException|BaseException|InvalidArgumentException */ public function deleteMultiple($keys): bool { @@ -190,6 +177,21 @@ public function deleteMultiple($keys): bool return !in_array(false, $results, true); } + /** + * @inheritDoc + * @throws ModelException|BaseException + */ + public function delete($key): bool + { + $cacheItem = $this->cacheModel->findOneBy('key', $this->keyHash($key)); + + if ($cacheItem !== null && !empty($cacheItem->asArray())) { + return $cacheItem->delete(); + } + + return false; + } + /** * @inheritDoc */ @@ -197,5 +199,4 @@ public function clear(): bool { return $this->cacheModel->deleteMany(); } - } diff --git a/src/Cache/Factories/CacheFactory.php b/src/Cache/Factories/CacheFactory.php index aeff67f2..4b46fca1 100644 --- a/src/Cache/Factories/CacheFactory.php +++ b/src/Cache/Factories/CacheFactory.php @@ -50,10 +50,7 @@ class CacheFactory private array $instances = []; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public static function get(?string $adapter = null): Cache { @@ -61,10 +58,7 @@ public static function get(?string $adapter = null): Cache } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public function resolve(?string $adapter = null): Cache { @@ -84,7 +78,7 @@ public function resolve(?string $adapter = null): Cache } /** - * @throws CacheException + * @throws CacheException|BaseException */ private function createInstance(string $adapterClass, string $adapter): Cache { diff --git a/src/Cache/Helpers/cache.php b/src/Cache/Helpers/cache.php index 82f44b16..164c4e11 100644 --- a/src/Cache/Helpers/cache.php +++ b/src/Cache/Helpers/cache.php @@ -19,10 +19,7 @@ use Quantum\Cache\Cache; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ function cache(?string $adapter = null): Cache { diff --git a/src/Cache/Traits/CacheTrait.php b/src/Cache/Traits/CacheTrait.php index de64e695..b886703a 100644 --- a/src/Cache/Traits/CacheTrait.php +++ b/src/Cache/Traits/CacheTrait.php @@ -25,15 +25,9 @@ */ trait CacheTrait { - /** - * @var int - */ - protected $ttl; + protected int $ttl; - /** - * @var string - */ - protected $prefix; + protected string $prefix; /** * Gets the hashed key diff --git a/src/Captcha/Adapters/HcaptchaAdapter.php b/src/Captcha/Adapters/HcaptchaAdapter.php index a918fd44..313e170a 100644 --- a/src/Captcha/Adapters/HcaptchaAdapter.php +++ b/src/Captcha/Adapters/HcaptchaAdapter.php @@ -32,10 +32,7 @@ class HcaptchaAdapter implements CaptchaInterface public const CLIENT_API = 'https://hcaptcha.com/1/api.js?onload=onLoadCallback&recaptchacompat=off'; - /** - * @var string - */ - protected $name = 'h-captcha'; + protected string $name = 'h-captcha'; /** * @var string[] diff --git a/src/Captcha/Adapters/RecaptchaAdapter.php b/src/Captcha/Adapters/RecaptchaAdapter.php index cb4b9a7e..e7a098c5 100644 --- a/src/Captcha/Adapters/RecaptchaAdapter.php +++ b/src/Captcha/Adapters/RecaptchaAdapter.php @@ -32,10 +32,7 @@ class RecaptchaAdapter implements CaptchaInterface public const CLIENT_API = 'https://www.google.com/recaptcha/api.js'; - /** - * @var string - */ - protected $name = 'g-recaptcha'; + protected string $name = 'g-recaptcha'; /** * @var string[] diff --git a/src/Captcha/Factories/CaptchaFactory.php b/src/Captcha/Factories/CaptchaFactory.php index 820c3e56..05a2b5ec 100644 --- a/src/Captcha/Factories/CaptchaFactory.php +++ b/src/Captcha/Factories/CaptchaFactory.php @@ -58,10 +58,7 @@ public static function get(?string $adapter = null): Captcha } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public function resolve(?string $adapter = null): Captcha { @@ -81,7 +78,7 @@ public function resolve(?string $adapter = null): Captcha } /** - * @throws CaptchaException + * @throws CaptchaException|BaseException */ private function createInstance(string $adapterClass, string $adapter): Captcha { diff --git a/src/Captcha/Helpers/captcha.php b/src/Captcha/Helpers/captcha.php index 8fe75d26..4a8c05ab 100644 --- a/src/Captcha/Helpers/captcha.php +++ b/src/Captcha/Helpers/captcha.php @@ -19,10 +19,7 @@ use Quantum\Captcha\Captcha; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ function captcha(?string $adapter = null): Captcha { diff --git a/src/Captcha/Traits/CaptchaTrait.php b/src/Captcha/Traits/CaptchaTrait.php index 5d0d6dc8..6345e84d 100644 --- a/src/Captcha/Traits/CaptchaTrait.php +++ b/src/Captcha/Traits/CaptchaTrait.php @@ -20,7 +20,6 @@ use Quantum\Asset\Exceptions\AssetException; use Quantum\Lang\Exceptions\LangException; use Quantum\Http\Exceptions\HttpException; -use Quantum\App\Exceptions\BaseException; use Quantum\HttpClient\HttpClient; use Quantum\Asset\Asset; use ErrorException; @@ -99,11 +98,7 @@ public function addToForm(string $formIdentifier = '', array $attributes = []): /** * Checks the code given by the captcha - * @throws LangException - * @throws ErrorException - * @throws BaseException - * @throws HttpException - * @throws Exception + * @throws LangException|HttpException|ErrorException|Exception */ public function verify(string $code): bool { @@ -192,9 +187,6 @@ function onSubmit (){ '; } - /** - * @param string $type - */ protected function isValidCaptchaType(string $type): bool { $captchaTypes = [ diff --git a/src/Config/Helpers/config.php b/src/Config/Helpers/config.php index b9ae41bb..f4395648 100644 --- a/src/Config/Helpers/config.php +++ b/src/Config/Helpers/config.php @@ -14,9 +14,11 @@ use Quantum\Config\Config; use Quantum\Di\Di; +use Quantum\Di\Exceptions\DiException; /** * Config facade + * @throws DiException|ReflectionException */ function config(): Config { diff --git a/src/Console/Commands/CronRunCommand.php b/src/Console/Commands/CronRunCommand.php index e60aac9a..c6c22c50 100644 --- a/src/Console/Commands/CronRunCommand.php +++ b/src/Console/Commands/CronRunCommand.php @@ -16,9 +16,14 @@ namespace Quantum\Console\Commands; +use Quantum\Config\Exceptions\ConfigException; use Quantum\Cron\Exceptions\CronException; +use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Console\QtCommand; use Quantum\Cron\CronManager; +use ReflectionException; +use Throwable; /** * Class CronRunCommand @@ -70,13 +75,14 @@ public function exec(): void } } catch (CronException $e) { $this->error($e->getMessage()); - } catch (\Throwable $e) { + } catch (Throwable $e) { $this->error('Unexpected error: ' . $e->getMessage()); } } /** * Run all due tasks + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ private function runAllDueTasks(CronManager $manager, bool $force): void { @@ -111,7 +117,7 @@ private function runAllDueTasks(CronManager $manager, bool $force): void /** * Run a specific task - * @throws CronException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ private function runSpecificTask(CronManager $manager, string $taskName, bool $force): void { diff --git a/src/Console/Commands/InstallToolkitCommand.php b/src/Console/Commands/InstallToolkitCommand.php index 82ab3878..edb6f18f 100644 --- a/src/Console/Commands/InstallToolkitCommand.php +++ b/src/Console/Commands/InstallToolkitCommand.php @@ -20,8 +20,12 @@ use Quantum\Environment\Exceptions\EnvException; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Input\ArrayInput; +use Quantum\Config\Exceptions\ConfigException; +use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Environment\Environment; use Quantum\Console\QtCommand; +use ReflectionException; use RuntimeException; /** @@ -60,8 +64,7 @@ class InstallToolkitCommand extends QtCommand /** * Executes the command - * @throws ExceptionInterface - * @throws EnvException + * @throws EnvException|ConfigException|BaseException|DiException|ExceptionInterface|ReflectionException */ public function exec(): void { diff --git a/src/Console/Commands/MigrationGenerateCommand.php b/src/Console/Commands/MigrationGenerateCommand.php index 4740d9de..1cb21f8e 100644 --- a/src/Console/Commands/MigrationGenerateCommand.php +++ b/src/Console/Commands/MigrationGenerateCommand.php @@ -17,7 +17,6 @@ namespace Quantum\Console\Commands; use Quantum\Migration\Exceptions\MigrationException; -use Quantum\Lang\Exceptions\LangException; use Quantum\Migration\MigrationManager; use Quantum\Console\QtCommand; @@ -48,7 +47,6 @@ class MigrationGenerateCommand extends QtCommand /** * Executes the command - * @throws LangException * @throws MigrationException */ public function exec(): void diff --git a/src/Console/Commands/MigrationMigrateCommand.php b/src/Console/Commands/MigrationMigrateCommand.php index c001edbf..57e1d329 100644 --- a/src/Console/Commands/MigrationMigrateCommand.php +++ b/src/Console/Commands/MigrationMigrateCommand.php @@ -18,11 +18,8 @@ use Quantum\Migration\Exceptions\MigrationException; use Quantum\Database\Exceptions\DatabaseException; -use Quantum\Config\Exceptions\ConfigException; -use Quantum\Lang\Exceptions\LangException; use Quantum\App\Exceptions\BaseException; use Quantum\Migration\MigrationManager; -use Quantum\Di\Exceptions\DiException; use Quantum\Console\QtCommand; /** @@ -59,11 +56,7 @@ class MigrationMigrateCommand extends QtCommand /** * Executes the command - * @throws BaseException - * @throws ConfigException - * @throws DatabaseException - * @throws DiException - * @throws LangException + * @throws DatabaseException|BaseException */ public function exec(): void { diff --git a/src/Console/Commands/OpenApiCommand.php b/src/Console/Commands/OpenApiCommand.php index 1785fcc3..08b37d70 100644 --- a/src/Console/Commands/OpenApiCommand.php +++ b/src/Console/Commands/OpenApiCommand.php @@ -89,11 +89,7 @@ public function __construct() /** * Executes the command and generate Open API specifications - * @throws BaseException - * @throws ModuleException - * @throws RouteException - * @throws DiException - * @throws ReflectionException + * @throws ModuleException|RouteException|DiException|BaseException|ReflectionException */ public function exec(): void { diff --git a/src/Console/Commands/ResourceCacheClearCommand.php b/src/Console/Commands/ResourceCacheClearCommand.php index 631b135f..25ccebb5 100644 --- a/src/Console/Commands/ResourceCacheClearCommand.php +++ b/src/Console/Commands/ResourceCacheClearCommand.php @@ -85,9 +85,6 @@ public function __construct() $this->fs = FileSystemFactory::get(); } - /** - * @throws BaseException|ReflectionException - */ public function exec(): void { try { @@ -133,9 +130,7 @@ private function importModules(): void } /** - * @throws ConfigException - * @throws DiException - * @throws ReflectionException|LoaderException + * @throws LoaderException|ConfigException|DiException|ReflectionException */ private function importConfig(): void { diff --git a/src/Console/Commands/RouteListCommand.php b/src/Console/Commands/RouteListCommand.php index c204c164..7940dd3a 100644 --- a/src/Console/Commands/RouteListCommand.php +++ b/src/Console/Commands/RouteListCommand.php @@ -25,6 +25,7 @@ use Quantum\Module\ModuleLoader; use Quantum\Console\QtCommand; use Quantum\Router\Route; +use ReflectionException; use Quantum\Di\Di; /** @@ -53,7 +54,7 @@ class RouteListCommand extends QtCommand /** * Executes the command - * @throws DiException + * @throws DiException|ReflectionException */ public function exec(): void { diff --git a/src/Cookie/Helpers/cookie.php b/src/Cookie/Helpers/cookie.php index c22db8c5..5e72a212 100644 --- a/src/Cookie/Helpers/cookie.php +++ b/src/Cookie/Helpers/cookie.php @@ -12,11 +12,13 @@ * @since 3.0.0 */ +use Quantum\Di\Exceptions\DiException; use Quantum\Cookie\Cookie; use Quantum\Di\Di; /** * Gets cookie handler + * @throws DiException|ReflectionException */ function cookie(): Cookie { diff --git a/src/Cron/CronLock.php b/src/Cron/CronLock.php index 5e55841f..862f4f44 100644 --- a/src/Cron/CronLock.php +++ b/src/Cron/CronLock.php @@ -38,7 +38,7 @@ class CronLock private const DEFAULT_MAX_LOCK_AGE = 86400; /** - * @throws BaseException|ConfigException|CronException|DiException|ReflectionException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ public function __construct(string $taskName, ?string $lockDirectory = null, ?int $maxLockAge = null) { @@ -104,10 +104,7 @@ public function getTimestamp(): int } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function release(): bool { @@ -131,10 +128,7 @@ public function release(): bool /** * Check if another process currently holds the lock. - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function isLocked(): bool { @@ -184,11 +178,7 @@ private function getDefaultLockDirectory(): string } /** - * @throws BaseException - * @throws ConfigException - * @throws CronException - * @throws DiException - * @throws ReflectionException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ private function ensureLockDirectoryExists(): void { @@ -204,11 +194,7 @@ private function ensureLockDirectoryExists(): void } /** - * @throws BaseException - * @throws ConfigException - * @throws CronException - * @throws DiException - * @throws ReflectionException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ private function createDirectory(string $directory): void { @@ -228,10 +214,7 @@ private function createDirectory(string $directory): void } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private function cleanupStaleLocks(): void { diff --git a/src/Cron/CronManager.php b/src/Cron/CronManager.php index f4576708..87275b93 100644 --- a/src/Cron/CronManager.php +++ b/src/Cron/CronManager.php @@ -24,6 +24,7 @@ use Quantum\Di\Exceptions\DiException; use Quantum\Logger\Enums\LoggerType; use ReflectionException; +use Throwable; /** * Class CronManager @@ -56,8 +57,8 @@ class CronManager ]; /** - * CronManager constructor - */ + * @throws DiException|ReflectionException + */ public function __construct(?string $cronDirectory = null) { $configuredPath = $cronDirectory ?? cron_config('path'); @@ -66,10 +67,6 @@ public function __construct(?string $cronDirectory = null) /** * Load tasks from the cron directory - * @return void - * @throws CronException - */ - /** * @throws CronException|BaseException|ConfigException|DiException|ReflectionException */ public function loadTasks(): void @@ -133,7 +130,7 @@ private function createTaskFromArray(array $definition): CronTask */ /** * @return array|int[] - * @throws BaseException|ConfigException|CronException|DiException|ReflectionException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ public function runDueTasks(bool $force = false): array { @@ -152,7 +149,7 @@ public function runDueTasks(bool $force = false): array /** * Run a specific task by name - * @throws BaseException|ConfigException|CronException|DiException|ReflectionException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ public function runTaskByName(string $taskName, bool $force = false): void { @@ -167,7 +164,7 @@ public function runTaskByName(string $taskName, bool $force = false): void /** * Run a single task - * @throws BaseException|ConfigException|CronException|DiException|ReflectionException + * @throws CronException|ConfigException|DiException|BaseException|ReflectionException */ private function runTask(CronTaskInterface $task, bool $force = false): void { @@ -187,7 +184,7 @@ private function runTask(CronTaskInterface $task, bool $force = false): void $duration = round(microtime(true) - $startTime, 2); $this->stats['executed']++; $this->log('info', "Task \"{$task->getName()}\" completed in {$duration}s"); - } catch (\Throwable $e) { + } catch (Throwable $e) { $this->stats['failed']++; $this->log('error', "Task \"{$task->getName()}\" failed: " . $e->getMessage(), [ 'exception' => get_class($e), @@ -236,7 +233,7 @@ private function log(string $level, string $message, array $context = []): void try { $logger = LoggerFactory::get(LoggerType::SINGLE); $logger->log($level, '[CRON] ' . $message, $context); - } catch (\Throwable $exception) { + } catch (Throwable $exception) { error_log(sprintf('[CRON] [%s] %s', strtoupper($level), $message)); } } diff --git a/src/Cron/CronTask.php b/src/Cron/CronTask.php index de942111..efa9ad4a 100644 --- a/src/Cron/CronTask.php +++ b/src/Cron/CronTask.php @@ -19,6 +19,7 @@ use Quantum\Cron\Contracts\CronTaskInterface; use Quantum\Cron\Exceptions\CronException; use Cron\CronExpression; +use Exception; /** * Class CronTask @@ -53,7 +54,7 @@ public function __construct(string $name, string $expression, callable $callback try { $this->cronExpression = new CronExpression($expression); - } catch (\Exception $e) { + } catch (Exception $e) { throw CronException::invalidExpression($expression); } } @@ -100,6 +101,7 @@ public function getNextRunDate(): \DateTime /** * Get the previous run date + * @throws Exception */ public function getPreviousRunDate(): \DateTime { diff --git a/src/Cron/Helpers/cron.php b/src/Cron/Helpers/cron.php index feb3c925..b83d57f6 100644 --- a/src/Cron/Helpers/cron.php +++ b/src/Cron/Helpers/cron.php @@ -12,6 +12,8 @@ * @since 3.0.0 */ +use Quantum\Cron\Exceptions\CronException; +use Quantum\Di\Exceptions\DiException; use Quantum\Cron\CronManager; use Quantum\Cron\CronTask; use Quantum\Cron\Schedule; @@ -21,7 +23,8 @@ /** * Resolve cron configuration value * @param mixed $default - * @return mixed|null + * @return mixed + * @throws DiException|ReflectionException */ function cron_config(string $key, $default = null) { @@ -32,7 +35,7 @@ function cron_config(string $key, $default = null) if (!config()->has('cron')) { config()->import(new Setup('config', 'cron')); } - } catch (\Throwable $exception) { + } catch (Throwable $exception) { // Ignore missing cron config file and rely on defaults } @@ -56,7 +59,7 @@ function cron_manager(?string $cronDirectory = null): CronManager if (!function_exists('cron_task')) { /** * Create a new cron task - * @throws \Quantum\Cron\Exceptions\CronException + * @throws CronException */ function cron_task(string $name, string $expression, callable $callback): CronTask { diff --git a/src/Database/Adapters/Idiorm/IdiormDbal.php b/src/Database/Adapters/Idiorm/IdiormDbal.php index 1106fd41..05656422 100644 --- a/src/Database/Adapters/Idiorm/IdiormDbal.php +++ b/src/Database/Adapters/Idiorm/IdiormDbal.php @@ -30,6 +30,8 @@ use InvalidArgumentException; use ORM; use PDO; +use Quantum\Di\Exceptions\DiException; +use ReflectionException; /** * Class IdiormDbal @@ -257,6 +259,7 @@ protected function updateOrmModel(ORM $ormModel) /** * @param array $config * @return array + * @throws DiException|ReflectionException */ protected static function getBaseConfig(string $driver, array $config): array { diff --git a/src/Database/Adapters/Idiorm/IdiormPatch.php b/src/Database/Adapters/Idiorm/IdiormPatch.php index 008635ae..3bca280c 100644 --- a/src/Database/Adapters/Idiorm/IdiormPatch.php +++ b/src/Database/Adapters/Idiorm/IdiormPatch.php @@ -16,8 +16,8 @@ namespace Quantum\Database\Adapters\Idiorm; -use ORM; use RuntimeException; +use ORM; /** * Class IdiormPatch diff --git a/src/Database/Adapters/Idiorm/Statements/Criteria.php b/src/Database/Adapters/Idiorm/Statements/Criteria.php index 8f09c4ca..a24a905e 100644 --- a/src/Database/Adapters/Idiorm/Statements/Criteria.php +++ b/src/Database/Adapters/Idiorm/Statements/Criteria.php @@ -18,6 +18,7 @@ use Quantum\Database\Exceptions\DatabaseException; use Quantum\Database\Contracts\DbalInterface; +use Quantum\App\Exceptions\BaseException; /** * Trait Criteria @@ -27,7 +28,7 @@ trait Criteria { /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public function criteria(string $column, string $operator, $value = null): DbalInterface { @@ -41,7 +42,7 @@ public function criteria(string $column, string $operator, $value = null): DbalI /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public function criterias(...$criterias): DbalInterface { @@ -58,7 +59,7 @@ public function criterias(...$criterias): DbalInterface /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public function having(string $column, string $operator, ?string $value = null): DbalInterface { @@ -73,7 +74,7 @@ public function having(string $column, string $operator, ?string $value = null): /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public function isNull(string $column): DbalInterface { @@ -83,7 +84,7 @@ public function isNull(string $column): DbalInterface /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public function isNotNull(string $column): DbalInterface { @@ -94,8 +95,8 @@ public function isNotNull(string $column): DbalInterface /** * Adds Criteria * @param mixed $value - * @throws DatabaseException * @return void + * @throws DatabaseException|BaseException */ protected function addCriteria(string $column, string $operator, $value, ?string $func = null) { @@ -111,8 +112,8 @@ protected function addCriteria(string $column, string $operator, $value, ?string /** * Adds one or more OR criteria in brackets * @param array $orCriterias - * @throws DatabaseException * @return void + * @throws DatabaseException|BaseException */ protected function orCriteria(array $orCriterias) { diff --git a/src/Database/Adapters/Idiorm/Statements/Join.php b/src/Database/Adapters/Idiorm/Statements/Join.php index ddb1dd44..a366c078 100644 --- a/src/Database/Adapters/Idiorm/Statements/Join.php +++ b/src/Database/Adapters/Idiorm/Statements/Join.php @@ -77,8 +77,7 @@ public function rightJoin(string $table, array $constraint, ?string $tableAlias /** * @inheritDoc - * @throws BaseException - * @throws ModelException + * @throws ModelException|BaseException */ public function joinTo(DbModel $relatedModel, bool $switch = true): DbalInterface { diff --git a/src/Database/Adapters/Idiorm/Statements/Model.php b/src/Database/Adapters/Idiorm/Statements/Model.php index 42570422..175762a1 100644 --- a/src/Database/Adapters/Idiorm/Statements/Model.php +++ b/src/Database/Adapters/Idiorm/Statements/Model.php @@ -16,6 +16,7 @@ namespace Quantum\Database\Adapters\Idiorm\Statements; +use Quantum\App\Exceptions\BaseException; use Quantum\Database\Exceptions\DatabaseException; use Quantum\Database\Contracts\DbalInterface; @@ -27,7 +28,7 @@ trait Model { /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public function create(): DbalInterface { diff --git a/src/Database/Adapters/Idiorm/Statements/Query.php b/src/Database/Adapters/Idiorm/Statements/Query.php index 15557a98..db553b5c 100644 --- a/src/Database/Adapters/Idiorm/Statements/Query.php +++ b/src/Database/Adapters/Idiorm/Statements/Query.php @@ -17,6 +17,7 @@ namespace Quantum\Database\Adapters\Idiorm\Statements; use Quantum\Database\Exceptions\DatabaseException; +use Quantum\App\Exceptions\BaseException; use PDOStatement; use PDOException; @@ -28,7 +29,7 @@ trait Query { /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public static function execute(string $query, array $parameters = []): bool { @@ -48,7 +49,7 @@ public static function execute(string $query, array $parameters = []): bool /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public static function query(string $query, array $parameters = []): array { @@ -70,7 +71,7 @@ public static function query(string $query, array $parameters = []): array /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public static function lastQuery(): ?string { @@ -83,7 +84,7 @@ public static function lastQuery(): ?string /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public static function lastStatement(): object { @@ -96,7 +97,7 @@ public static function lastStatement(): object /** * @inheritDoc - * @throws DatabaseException + * @throws DatabaseException|BaseException */ public static function queryLog(): array { @@ -109,8 +110,8 @@ public static function queryLog(): array /** * Fetches columns of the table - * @throws DatabaseException * @return array + * @throws DatabaseException|BaseException */ public static function fetchColumns(string $table): array { diff --git a/src/Database/Adapters/Sleekdb/SleekDbal.php b/src/Database/Adapters/Sleekdb/SleekDbal.php index 9e2e6af5..1cd37d49 100644 --- a/src/Database/Adapters/Sleekdb/SleekDbal.php +++ b/src/Database/Adapters/Sleekdb/SleekDbal.php @@ -120,7 +120,7 @@ class SleekDbal implements DbalInterface * Hidden fields * @var array */ - public $hidden = []; + public array $hidden = []; /** * ORM Model diff --git a/src/Database/Adapters/Sleekdb/Statements/Model.php b/src/Database/Adapters/Sleekdb/Statements/Model.php index 5bd430a7..46496aa7 100644 --- a/src/Database/Adapters/Sleekdb/Statements/Model.php +++ b/src/Database/Adapters/Sleekdb/Statements/Model.php @@ -82,7 +82,7 @@ public function save(): bool * @throws DatabaseException * @throws IOException * @throws InvalidArgumentException - * @throws InvalidConfigurationException + * @throws InvalidConfigurationException|BaseException */ public function delete(): bool { @@ -96,7 +96,7 @@ public function delete(): bool * @throws IOException * @throws InvalidArgumentException * @throws InvalidConfigurationException - * @throws ModelException + * @throws ModelException|BaseException */ public function deleteMany(): bool { diff --git a/src/Database/Factories/TableFactory.php b/src/Database/Factories/TableFactory.php index e083b416..8439f250 100644 --- a/src/Database/Factories/TableFactory.php +++ b/src/Database/Factories/TableFactory.php @@ -19,6 +19,7 @@ use Quantum\Database\Exceptions\DatabaseException; use Quantum\Database\Schemas\Table; use Quantum\Database\Database; +use Exception; /** * Class TableFactory @@ -87,7 +88,7 @@ public function checkTableExists(string $name): bool { try { Database::query('SELECT 1 FROM ' . $name); - } catch (DatabaseException $e) { + } catch (Exception $e) { return false; } diff --git a/src/Database/Traits/RelationalTrait.php b/src/Database/Traits/RelationalTrait.php index bc30e00e..35f7aac8 100644 --- a/src/Database/Traits/RelationalTrait.php +++ b/src/Database/Traits/RelationalTrait.php @@ -16,9 +16,10 @@ namespace Quantum\Database\Traits; -use Quantum\Database\Exceptions\DatabaseException; use Quantum\Database\Database; use Quantum\Di\Di; +use Quantum\Di\Exceptions\DiException; +use ReflectionException; /** * Trait RelationalTrait @@ -29,7 +30,7 @@ trait RelationalTrait /** * Raw execute * @param array $parameters - * @throws DatabaseException + * @throws DiException|ReflectionException */ public static function execute(string $query, array $parameters = []): bool { @@ -40,7 +41,7 @@ public static function execute(string $query, array $parameters = []): bool * Raw query * @param array $parameters * @return array - * @throws DatabaseException + * @throws DiException|ReflectionException */ public static function query(string $query, array $parameters = []): array { @@ -51,7 +52,7 @@ public static function query(string $query, array $parameters = []): array * Fetches table columns * @param array $parameters * @return array - * @throws DatabaseException + * @throws DiException|ReflectionException */ public static function fetchColumns(string $query, array $parameters = []): array { @@ -60,7 +61,7 @@ public static function fetchColumns(string $query, array $parameters = []): arra /** * Gets the last query executed - * @throws DatabaseException + * @throws DiException|ReflectionException */ public static function lastQuery(): ?string { @@ -71,7 +72,7 @@ public static function lastQuery(): ?string * Get an array containing all the queries * run on a specified connection up to now. * @return array - * @throws DatabaseException + * @throws DiException|ReflectionException */ public static function queryLog(): array { @@ -82,7 +83,7 @@ public static function queryLog(): array * Resolves the requested query * @param array $parameters * @return mixed - * @throws DatabaseException + * @throws DiException|ReflectionException */ protected static function resolveQuery(string $method, string $query = '', array $parameters = []) { diff --git a/src/Debugger/Debugger.php b/src/Debugger/Debugger.php index 457b1999..0c28d770 100644 --- a/src/Debugger/Debugger.php +++ b/src/Debugger/Debugger.php @@ -21,8 +21,10 @@ use DebugBar\DataCollector\MessagesCollector; use DebugBar\DataCollector\PhpInfoCollector; use DebugBar\DataCollector\MemoryCollector; +use Quantum\Di\Exceptions\DiException; use DebugBar\JavascriptRenderer; use DebugBar\DebugBarException; +use ReflectionException; use DebugBar\DebugBar; /** @@ -95,6 +97,7 @@ public function __construct(?DebuggerStore $store = null, ?DebugBar $debugBar = /** * Checks if debug bar enabled + * @throws DiException|ReflectionException */ public function isEnabled(): bool { diff --git a/src/Di/Di.php b/src/Di/Di.php index 8d2ecb4d..cb9f7087 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -171,8 +171,7 @@ public static function create(string $dependency, array $args = []) * Autowire callable parameters * @param array $args * @return array - * @throws DiException - * @throws ReflectionException + * @throws DiException|ReflectionException */ public static function autowire(callable $entry, array $args = []): array { @@ -252,7 +251,7 @@ private static function instantiate(string $concrete, array $args = []) * @param array $parameters * @param array $args * @return array - * @throws DiException + * @throws DiException|ReflectionException */ private static function resolveParameters(array $parameters, array &$args = []): array { diff --git a/src/Encryption/Adapters/AsymmetricEncryptionAdapter.php b/src/Encryption/Adapters/AsymmetricEncryptionAdapter.php index d42d0104..0953e500 100644 --- a/src/Encryption/Adapters/AsymmetricEncryptionAdapter.php +++ b/src/Encryption/Adapters/AsymmetricEncryptionAdapter.php @@ -36,15 +36,9 @@ class AsymmetricEncryptionAdapter implements EncryptionInterface */ public const KEY_BITS = 1024; - /** - * @var string - */ - private $publicKey; + private ?string $publicKey = null; - /** - * @var string - */ - private $privateKey; + private ?string $privateKey = null; /** * @throws BaseException diff --git a/src/Encryption/Adapters/SymmetricEncryptionAdapter.php b/src/Encryption/Adapters/SymmetricEncryptionAdapter.php index 5d1aed22..2596000e 100644 --- a/src/Encryption/Adapters/SymmetricEncryptionAdapter.php +++ b/src/Encryption/Adapters/SymmetricEncryptionAdapter.php @@ -20,6 +20,7 @@ use Quantum\Encryption\Exceptions\CryptorException; use Quantum\App\Exceptions\BaseException; use Quantum\App\Exceptions\AppException; +use ReflectionException; /** * Class SymmetricEncryptionAdapter @@ -32,13 +33,10 @@ class SymmetricEncryptionAdapter implements EncryptionInterface */ public const CIPHER_METHOD = 'aes-256-cbc'; - /** - * @var string - */ - private $appKey; + private string $appKey; /** - * @throws BaseException + * @throws BaseException|ReflectionException */ public function __construct() { @@ -53,6 +51,7 @@ public function __construct() /** * Encrypts the string + * @throws CryptorException */ public function encrypt(string $plain): string { @@ -97,6 +96,7 @@ public function decrypt(string $encrypted): string /** * Generates initialization vector + * @throws CryptorException */ private function generateIV(): string { diff --git a/src/Encryption/Factories/CryptorFactory.php b/src/Encryption/Factories/CryptorFactory.php index e9dcb5fb..7096b23f 100644 --- a/src/Encryption/Factories/CryptorFactory.php +++ b/src/Encryption/Factories/CryptorFactory.php @@ -22,6 +22,7 @@ use Quantum\App\Exceptions\BaseException; use Quantum\Encryption\Enums\CryptorType; use Quantum\Encryption\Cryptor; +use ReflectionException; use Quantum\Di\Di; /** @@ -41,7 +42,7 @@ class CryptorFactory private array $instances = []; /** - * @throws BaseException + * @throws BaseException|ReflectionException */ public static function get(string $type = CryptorType::SYMMETRIC): Cryptor { diff --git a/src/Encryption/Helpers/encryption.php b/src/Encryption/Helpers/encryption.php index e8194c25..39b9bdef 100644 --- a/src/Encryption/Helpers/encryption.php +++ b/src/Encryption/Helpers/encryption.php @@ -19,7 +19,7 @@ /** * Encodes the data cryptographically * @param mixed $data - * @throws BaseException + * @throws BaseException|ReflectionException */ function crypto_encode($data, string $type = CryptorType::SYMMETRIC): string { @@ -30,7 +30,7 @@ function crypto_encode($data, string $type = CryptorType::SYMMETRIC): string /** * @return mixed|string - * @throws BaseException + * @throws BaseException|ReflectionException */ function crypto_decode(string $encryptedData, string $type = CryptorType::SYMMETRIC) { diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index 074c6cee..0d8ae4ef 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -77,10 +77,7 @@ public function setMutable(bool $isMutable): Environment /** * Loads environment variables from file - * @throws BaseException - * @throws EnvException - * @throws DiException - * @throws ReflectionException + * @throws EnvException|DiException|BaseException|ReflectionException */ public function load(Setup $setup): void { @@ -149,11 +146,7 @@ public function getRow(string $key): ?string /** * Creates or updates the row in .env - * @throws BaseException - * @throws DiException - * @throws EnvException - * @throws ReflectionException - * @throws ConfigException + * @throws EnvException|ConfigException|DiException|BaseException|ReflectionException */ public function updateRow(string $key, ?string $value): void { diff --git a/src/Environment/Helpers/server.php b/src/Environment/Helpers/server.php index b63b3171..4b93c7b1 100644 --- a/src/Environment/Helpers/server.php +++ b/src/Environment/Helpers/server.php @@ -12,9 +12,13 @@ * @since 3.0.0 */ +use Quantum\Di\Exceptions\DiException; use Quantum\Environment\Server; use Quantum\Di\Di; +/** + * @throws DiException|ReflectionException + */ function server(): Server { return Di::get(Server::class); @@ -29,6 +33,7 @@ function get_user_ip(): ?string /** * @return array + * @throws DiException|ReflectionException */ function getallheaders(): array { diff --git a/src/Hook/Helpers/hook.php b/src/Hook/Helpers/hook.php index 4414311c..5b745457 100644 --- a/src/Hook/Helpers/hook.php +++ b/src/Hook/Helpers/hook.php @@ -12,11 +12,13 @@ * @since 3.0.0 */ +use Quantum\Di\Exceptions\DiException; use Quantum\Hook\HookManager; use Quantum\Di\Di; /** * Gets the HookManager instance + * @throws DiException|ReflectionException */ function hook(): HookManager { diff --git a/src/Hook/HookManager.php b/src/Hook/HookManager.php index fbf8b2b1..11f3c54f 100644 --- a/src/Hook/HookManager.php +++ b/src/Hook/HookManager.php @@ -41,10 +41,7 @@ class HookManager private array $store = []; /** - * @throws HookException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException|LoaderException + * @throws HookException|ConfigException|DiException|LoaderException|ReflectionException */ public function __construct() { @@ -100,7 +97,6 @@ public function getRegistered(): array /** * Registers new hook - * @return void * @throws HookException */ protected function register(string $name): void diff --git a/src/Http/Helpers/http.php b/src/Http/Helpers/http.php index cb6bf96b..9b680e99 100644 --- a/src/Http/Helpers/http.php +++ b/src/Http/Helpers/http.php @@ -18,7 +18,6 @@ use Quantum\App\Enums\ReservedKeys; use Quantum\Http\Enums\ContentType; use Quantum\Http\Enums\StatusCode; -use DebugBar\DebugBarException; use Quantum\Http\Response; use Quantum\Http\Request; @@ -51,10 +50,7 @@ function redirect(string $url, int $code = StatusCode::FOUND): void /** * Redirect with data * @param array $data - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function redirectWith(string $url, array $data, int $code = StatusCode::FOUND): void { @@ -65,10 +61,7 @@ function redirectWith(string $url, array $data, int $code = StatusCode::FOUND): /** * Gets old input values after redirect * @return mixed|null - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws BaseException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function old(string $key) { @@ -100,11 +93,7 @@ function get_referrer(): ?string /** * Handles page not found - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws DebugBarException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function page_not_found(): void { diff --git a/src/Http/Request/HttpRequest.php b/src/Http/Request/HttpRequest.php index 4369f68c..fc9490ca 100644 --- a/src/Http/Request/HttpRequest.php +++ b/src/Http/Request/HttpRequest.php @@ -75,10 +75,7 @@ abstract class HttpRequest /** * Initializes the request static properties using the server instance. - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function init(Server $server): void { @@ -259,6 +256,7 @@ private static function setContentType(): void /** * Sets request headers, normalizing keys to lowercase. + * @throws DiException|ReflectionException */ private static function setRequestHeaders(): void { diff --git a/src/Http/Response/HttpResponse.php b/src/Http/Response/HttpResponse.php index 4aaaa2f7..9de1f6e4 100644 --- a/src/Http/Response/HttpResponse.php +++ b/src/Http/Response/HttpResponse.php @@ -38,13 +38,13 @@ abstract class HttpResponse * XML root element * @var string */ - private static $xmlRoot = ''; + private static string $xmlRoot = ''; /** * Callback function * @var string */ - private static $callbackFunction = ''; + private static string $callbackFunction = ''; private static bool $initialized = false; diff --git a/src/Http/Traits/Request/Internal.php b/src/Http/Traits/Request/Internal.php index 164dd75a..da11a08c 100644 --- a/src/Http/Traits/Request/Internal.php +++ b/src/Http/Traits/Request/Internal.php @@ -36,10 +36,7 @@ trait Internal * @param array $params * @param array $headers * @param array $files - * @throws BaseException - * @throws ReflectionException - * @throws ConfigException - * @throws DiException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function create( string $method, diff --git a/src/Http/Traits/Request/Params.php b/src/Http/Traits/Request/Params.php index 6c050ce6..a0250d4f 100644 --- a/src/Http/Traits/Request/Params.php +++ b/src/Http/Traits/Request/Params.php @@ -97,10 +97,7 @@ private static function urlEncodedParams(): array /** * Parses and returns multipart form data parameters. * @return array - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private static function getRawInputParams(): array { diff --git a/src/Http/Traits/Request/RawInput.php b/src/Http/Traits/Request/RawInput.php index caea01e4..ad062ace 100644 --- a/src/Http/Traits/Request/RawInput.php +++ b/src/Http/Traits/Request/RawInput.php @@ -33,10 +33,7 @@ trait RawInput /** * Parses raw input data and returns parsed parameters and files * @return array - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function parse(string $rawInput): array { @@ -88,10 +85,7 @@ private static function getBlocks(string $boundary, string $rawInput): array * Processes multipart blocks and extracts parameters and files * @param array $blocks * @return array - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private static function processBlocks(array $blocks): array { @@ -186,10 +180,7 @@ private static function getParsedStream(string $block): array /** * Gets the parsed file * @return array{string, UploadedFile}|null - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private static function getParsedFile(string $block): ?array { diff --git a/src/Http/Traits/Response/Body.php b/src/Http/Traits/Response/Body.php index b322efb5..a1a0fc3d 100644 --- a/src/Http/Traits/Response/Body.php +++ b/src/Http/Traits/Response/Body.php @@ -109,7 +109,6 @@ public static function json(?array $data = null, ?int $code = null): void /** * Prepares the JSONP response - * @param string $callback * @param array|null $data */ public static function jsonp(string $callback, ?array $data = null, ?int $code = null): void diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 98431f5c..9125ec10 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -174,7 +174,7 @@ public function isMultiRequest(): bool /** * Starts the request * @throws ErrorException - * @throws HttpClientException + * @throws HttpClientException|BaseException */ public function start(): HttpClient { diff --git a/src/Jwt/JwtToken.php b/src/Jwt/JwtToken.php index 60805962..f97bcfb5 100644 --- a/src/Jwt/JwtToken.php +++ b/src/Jwt/JwtToken.php @@ -57,9 +57,8 @@ public function __construct(?string $key = null) /** * Sets extra leeway time - * @param int $leeway */ - public function setLeeway($leeway): JwtToken + public function setLeeway(int $leeway): JwtToken { parent::$leeway = $leeway; return $this; diff --git a/src/Lang/Factories/LangFactory.php b/src/Lang/Factories/LangFactory.php index 36804ce4..82b920e1 100644 --- a/src/Lang/Factories/LangFactory.php +++ b/src/Lang/Factories/LangFactory.php @@ -17,6 +17,7 @@ namespace Quantum\Lang\Factories; use Quantum\Config\Exceptions\ConfigException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Lang\Exceptions\LangException; use Quantum\Di\Exceptions\DiException; use Quantum\Lang\Translator; @@ -35,10 +36,7 @@ class LangFactory private ?Lang $instance = null; /** - * @throws ConfigException - * @throws LangException - * @throws DiException - * @throws ReflectionException + * @throws LangException|ConfigException|LoaderException|DiException|ReflectionException */ public static function get(): Lang { @@ -46,10 +44,7 @@ public static function get(): Lang } /** - * @throws ConfigException - * @throws LangException - * @throws DiException - * @throws ReflectionException + * @throws LangException|ConfigException|DiException|ReflectionException|LoaderException */ public function resolve(): Lang { @@ -68,9 +63,7 @@ public function resolve(): Lang /** * @return array{0: bool, 1: array, 2: string} - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws LoaderException|ConfigException|DiException|ReflectionException */ private function loadLangConfig(): array { @@ -87,7 +80,7 @@ private function loadLangConfig(): array /** * @param array $supported - * @throws LangException + * @throws LangException|DiException|ReflectionException */ private function detectLanguage(array $supported, ?string $default): string { @@ -124,6 +117,7 @@ private function getLangFromQuery(array $supported): ?string /** * @param array $supported + * @throws DiException|ReflectionException */ private function getLangFromUrlSegment(array $supported): ?string { @@ -140,6 +134,7 @@ private function getLangFromUrlSegment(array $supported): ?string /** * @param array $supported + * @throws DiException|ReflectionException */ private function getLangFromHeader(array $supported): ?string { diff --git a/src/Lang/Helpers/lang.php b/src/Lang/Helpers/lang.php index d61675bd..c0fef012 100644 --- a/src/Lang/Helpers/lang.php +++ b/src/Lang/Helpers/lang.php @@ -13,16 +13,14 @@ */ use Quantum\Config\Exceptions\ConfigException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Lang\Exceptions\LangException; use Quantum\Lang\Factories\LangFactory; use Quantum\Di\Exceptions\DiException; /** * Gets the current lang - * @throws ConfigException - * @throws DiException - * @throws LangException - * @throws ReflectionException + * @throws LangException|LoaderException|ConfigException|DiException|ReflectionException */ function current_lang(): ?string { @@ -31,12 +29,8 @@ function current_lang(): ?string /** * Gets translation - * @param string $key * @param array|string|null $params - * @throws ConfigException - * @throws ReflectionException - * @throws DiException - * @throws LangException + * @throws LangException|LoaderException|ConfigException|DiException|ReflectionException */ function t(string $key, $params = null): ?string { @@ -46,10 +40,7 @@ function t(string $key, $params = null): ?string /** * Outputs the translation * @param array|string|null $params - * @throws ConfigException - * @throws DiException - * @throws LangException - * @throws ReflectionException + * @throws LangException|LoaderException|ConfigException|DiException|ReflectionException */ function _t(string $key, $params = null): void { diff --git a/src/Lang/Lang.php b/src/Lang/Lang.php index c473177f..f8380a3a 100644 --- a/src/Lang/Lang.php +++ b/src/Lang/Lang.php @@ -17,6 +17,8 @@ namespace Quantum\Lang; use Quantum\Config\Exceptions\ConfigException; +use Quantum\Loader\Exceptions\LoaderException; +use Quantum\Lang\Exceptions\LangException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use ReflectionException; @@ -68,11 +70,7 @@ public function isEnabled(): bool /** * Load translations - * @throws Exceptions\LangException - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws LangException|LoaderException|ConfigException|DiException|BaseException|ReflectionException */ public function load(): void { diff --git a/src/Lang/Translator.php b/src/Lang/Translator.php index 3d5202eb..be07408c 100644 --- a/src/Lang/Translator.php +++ b/src/Lang/Translator.php @@ -17,6 +17,7 @@ namespace Quantum\Lang; use Quantum\Config\Exceptions\ConfigException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Lang\Exceptions\LangException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; @@ -40,11 +41,7 @@ public function __construct(string $lang) /** * Load translation files - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws LangException - * @throws ReflectionException + * @throws LangException|LoaderException|ConfigException|DiException|BaseException|ReflectionException */ public function loadTranslations(): void { @@ -79,10 +76,7 @@ public function loadTranslations(): void /** * Load translations * @param array $files - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private function loadFiles(array $files): void { diff --git a/src/Logger/Adapters/DailyAdapter.php b/src/Logger/Adapters/DailyAdapter.php index 4bb39097..bb9ac97b 100644 --- a/src/Logger/Adapters/DailyAdapter.php +++ b/src/Logger/Adapters/DailyAdapter.php @@ -36,11 +36,7 @@ class DailyAdapter implements ReportableInterface /** * DailyAdapter constructor * @param array $params - * @throws BaseException - * @throws LoggerException - * @throws DiException - * @throws ConfigException - * @throws ReflectionException + * @throws LoggerException|ConfigException|DiException|BaseException|ReflectionException */ public function __construct(array $params) { diff --git a/src/Logger/Adapters/MessageAdapter.php b/src/Logger/Adapters/MessageAdapter.php index 687e011f..c13c2b81 100644 --- a/src/Logger/Adapters/MessageAdapter.php +++ b/src/Logger/Adapters/MessageAdapter.php @@ -16,12 +16,12 @@ namespace Quantum\Logger\Adapters; -use Quantum\Di\Exceptions\DiException; use Quantum\Logger\Contracts\ReportableInterface; +use Quantum\Di\Exceptions\DiException; use DebugBar\DebugBarException; use Quantum\Debugger\Debugger; -use Quantum\Di\Di; use ReflectionException; +use Quantum\Di\Di; /** * Class MessageAdapter diff --git a/src/Logger/Adapters/SingleAdapter.php b/src/Logger/Adapters/SingleAdapter.php index 2c9d669d..2d042a7a 100644 --- a/src/Logger/Adapters/SingleAdapter.php +++ b/src/Logger/Adapters/SingleAdapter.php @@ -33,16 +33,10 @@ class SingleAdapter implements ReportableInterface { use LoggerTrait; - /** - * @throws BaseException - * @throws LoggerException - * @throws DiException - * @throws ConfigException - * @throws ReflectionException - */ /** * SingleAdapter constructor * @param array $params + * @throws LoggerException|ConfigException|DiException|BaseException|ReflectionException */ public function __construct(array $params) { diff --git a/src/Logger/Contracts/ReportableInterface.php b/src/Logger/Contracts/ReportableInterface.php index 699a2f7c..5f5da163 100644 --- a/src/Logger/Contracts/ReportableInterface.php +++ b/src/Logger/Contracts/ReportableInterface.php @@ -24,9 +24,7 @@ interface ReportableInterface { /** * Reports a message - * @param string $message * @param array|null $context - * @return void */ public function report(string $level, string $message, ?array $context = []): void; diff --git a/src/Logger/Factories/LoggerFactory.php b/src/Logger/Factories/LoggerFactory.php index f7527495..e2502e94 100644 --- a/src/Logger/Factories/LoggerFactory.php +++ b/src/Logger/Factories/LoggerFactory.php @@ -49,10 +49,7 @@ class LoggerFactory private array $instances = []; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function get(?string $adapter = null): Logger { @@ -60,10 +57,7 @@ public static function get(?string $adapter = null): Logger } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function resolve(?string $adapter = null): Logger { @@ -92,6 +86,9 @@ public function resolve(?string $adapter = null): Logger return $this->instances[$adapter]; } + /** + * @throws DiException|BaseException|ReflectionException + */ private function createInstance(string $adapterClass, string $adapter): Logger { if ($adapter === LoggerType::MESSAGE) { diff --git a/src/Logger/Helpers/logger.php b/src/Logger/Helpers/logger.php index 5a7d01d4..4131900d 100644 --- a/src/Logger/Helpers/logger.php +++ b/src/Logger/Helpers/logger.php @@ -19,10 +19,7 @@ use Quantum\Logger\Logger; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function logger(?string $adapter = null): Logger { @@ -31,12 +28,8 @@ function logger(?string $adapter = null): Logger /** * Reports error - * @param string $var * @param array $context - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function error(string $var, array $context = []): void { @@ -45,12 +38,8 @@ function error(string $var, array $context = []): void /** * Reports warning - * @param string $var * @param array $context - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function warning(string $var, array $context = []): void { @@ -59,12 +48,8 @@ function warning(string $var, array $context = []): void /** * Reports notice - * @param string $var * @param array $context - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function notice(string $var, array $context = []): void { @@ -73,12 +58,8 @@ function notice(string $var, array $context = []): void /** * Reports info - * @param string $var * @param array $context - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function info(string $var, array $context = []): void { @@ -87,12 +68,8 @@ function info(string $var, array $context = []): void /** * Reports debug - * @param string $var * @param array $context - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function debug(string $var, array $context = []): void { diff --git a/src/Logger/Traits/LoggerTrait.php b/src/Logger/Traits/LoggerTrait.php index 19159edd..593017b2 100644 --- a/src/Logger/Traits/LoggerTrait.php +++ b/src/Logger/Traits/LoggerTrait.php @@ -25,15 +25,9 @@ */ trait LoggerTrait { - /** - * @var FileSystem - */ - protected $fs; + protected FileSystem $fs; - /** - * @var string - */ - protected $logFile; + protected string $logFile; /** * Initialize the logger @@ -43,7 +37,6 @@ abstract protected function initialize(array $params): void; /** * Reports a log message - * @param string $message * @param array|null $context */ public function report(string $level, string $message, ?array $context = []): void diff --git a/src/Mailer/Adapters/MailgunAdapter.php b/src/Mailer/Adapters/MailgunAdapter.php index e4dccb62..1ae1c762 100644 --- a/src/Mailer/Adapters/MailgunAdapter.php +++ b/src/Mailer/Adapters/MailgunAdapter.php @@ -31,10 +31,7 @@ class MailgunAdapter implements MailerInterface public string $name = 'Mailgun'; - /** - * @var string - */ - private $apiKey; + private string $apiKey; private string $apiUrl = 'https://api.mailgun.net/v3/'; diff --git a/src/Mailer/Adapters/MandrillAdapter.php b/src/Mailer/Adapters/MandrillAdapter.php index 1b7c8797..e34b3400 100644 --- a/src/Mailer/Adapters/MandrillAdapter.php +++ b/src/Mailer/Adapters/MandrillAdapter.php @@ -29,10 +29,7 @@ class MandrillAdapter implements MailerInterface { use MailerTrait; - /** - * @var string - */ - public $name = 'Mandrill'; + public string $name = 'Mandrill'; private string $apiUrl = 'https://mandrillapp.com/api/1.0/messages/send.json'; diff --git a/src/Mailer/Adapters/ResendAdapter.php b/src/Mailer/Adapters/ResendAdapter.php index 51400ae0..50c4bce7 100644 --- a/src/Mailer/Adapters/ResendAdapter.php +++ b/src/Mailer/Adapters/ResendAdapter.php @@ -33,10 +33,7 @@ class ResendAdapter implements MailerInterface private HttpClient $httpClient; - /** - * @var string|null - */ - private $apiKey; + private string $apiKey; private string $apiUrl = 'https://api.resend.com/emails'; diff --git a/src/Mailer/Adapters/SendgridAdapter.php b/src/Mailer/Adapters/SendgridAdapter.php index 8201d7a0..7e4e0a9b 100644 --- a/src/Mailer/Adapters/SendgridAdapter.php +++ b/src/Mailer/Adapters/SendgridAdapter.php @@ -31,10 +31,7 @@ class SendgridAdapter implements MailerInterface public string $name = 'Sendgrid'; - /** - * @var string - */ - private $apiKey; + private string $apiKey; private string $apiUrl = 'https://api.sendgrid.com/v3/mail/send'; diff --git a/src/Mailer/Adapters/SendinblueAdapter.php b/src/Mailer/Adapters/SendinblueAdapter.php index bf0e9368..0ee1f30b 100644 --- a/src/Mailer/Adapters/SendinblueAdapter.php +++ b/src/Mailer/Adapters/SendinblueAdapter.php @@ -31,10 +31,7 @@ class SendinblueAdapter implements MailerInterface public string $name = 'Sendinblue'; - /** - * @var string - */ - private $apiKey; + private string $apiKey; private string $apiUrl = 'https://api.sendinblue.com/v3/smtp/email'; diff --git a/src/Mailer/Adapters/SmtpAdapter.php b/src/Mailer/Adapters/SmtpAdapter.php index 85c8cee5..923ab61c 100644 --- a/src/Mailer/Adapters/SmtpAdapter.php +++ b/src/Mailer/Adapters/SmtpAdapter.php @@ -16,12 +16,14 @@ namespace Quantum\Mailer\Adapters; +use Quantum\Di\Exceptions\DiException; use Quantum\Mailer\Contracts\MailerInterface; use Quantum\Mailer\Traits\MailerTrait; use PHPMailer\PHPMailer\PHPMailer; use Quantum\Debugger\Debugger; use PHPMailer\PHPMailer\SMTP; use Exception; +use ReflectionException; /** * class SmtpAdapter @@ -38,6 +40,7 @@ class SmtpAdapter implements MailerInterface /** * SmtpAdapter constructor * @param array $params + * @throws DiException|ReflectionException */ public function __construct(array $params) { diff --git a/src/Mailer/Factories/MailerFactory.php b/src/Mailer/Factories/MailerFactory.php index fdae16d0..bc08f1c0 100644 --- a/src/Mailer/Factories/MailerFactory.php +++ b/src/Mailer/Factories/MailerFactory.php @@ -54,10 +54,7 @@ class MailerFactory private array $instances = []; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function get(?string $adapter = null): Mailer { @@ -65,10 +62,7 @@ public static function get(?string $adapter = null): Mailer } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function resolve(?string $adapter = null): Mailer { @@ -88,7 +82,7 @@ public function resolve(?string $adapter = null): Mailer } /** - * @throws MailerException + * @throws DiException|BaseException|ReflectionException */ private function createInstance(string $adapterClass, string $adapter): Mailer { diff --git a/src/Middleware/MiddlewareManager.php b/src/Middleware/MiddlewareManager.php index 20130679..19e81777 100644 --- a/src/Middleware/MiddlewareManager.php +++ b/src/Middleware/MiddlewareManager.php @@ -17,6 +17,7 @@ namespace Quantum\Middleware; use Quantum\Middleware\Exceptions\MiddlewareException; +use Quantum\App\Exceptions\BaseException; use Quantum\Router\MatchedRoute; use Quantum\Http\Response; use Quantum\Http\Request; @@ -49,7 +50,7 @@ public function __construct(MatchedRoute $matchedRoute) /** * Apply Middlewares * @return array{0: Request, 1: Response} - * @throws MiddlewareException + * @throws MiddlewareException|BaseException */ public function applyMiddlewares(Request $request, Response $response): array { @@ -69,7 +70,7 @@ public function applyMiddlewares(Request $request, Response $response): array /** * Loads and gets the current middleware instance - * @throws MiddlewareException + * @throws MiddlewareException|BaseException */ private function getMiddleware(Request $request, Response $response): QtMiddleware { diff --git a/src/Migration/MigrationManager.php b/src/Migration/MigrationManager.php index 16991e18..08569b01 100644 --- a/src/Migration/MigrationManager.php +++ b/src/Migration/MigrationManager.php @@ -109,7 +109,7 @@ public function generateMigration(string $table, string $action): string /** * Applies migrations - * @throws MigrationException|DatabaseException + * @throws MigrationException|DatabaseException|BaseException|DiException|ReflectionException */ public function applyMigrations(string $direction, ?int $step = null): ?int { @@ -136,7 +136,7 @@ public function applyMigrations(string $direction, ?int $step = null): ?int /** * Runs up migrations - * @throws MigrationException|DatabaseException + * @throws MigrationException|DatabaseException|DiException|ReflectionException */ private function upgrade(): int { @@ -173,7 +173,7 @@ private function upgrade(): int /** * Runs down migrations - * @throws MigrationException|DatabaseException + * @throws MigrationException|DatabaseException|DiException|ReflectionException */ private function downgrade(?int $step): int { @@ -212,7 +212,7 @@ private function downgrade(?int $step): int /** * Prepares up migrations - * @throws MigrationException|DatabaseException + * @throws MigrationException|DiException|ReflectionException */ private function prepareUpMigrations(): void { @@ -237,7 +237,7 @@ private function prepareUpMigrations(): void /** * Prepares down migrations - * @throws MigrationException|DatabaseException + * @throws MigrationException|DiException|ReflectionException */ private function prepareDownMigrations(?int $step = null): void { @@ -282,7 +282,7 @@ private function getMigrationFiles(): array /** * Gets migrated entries from migrations table * @return array - * @throws DatabaseException + * @throws DiException|ReflectionException */ private function getMigratedEntries(): array { @@ -292,7 +292,7 @@ private function getMigratedEntries(): array /** * Adds migrated entries to migrations table * @param array $entries - * @throws DatabaseException + * @throws DiException|ReflectionException */ private function addMigratedEntries(array $entries): void { @@ -304,7 +304,7 @@ private function addMigratedEntries(array $entries): void /** * Removes migrated entries from migrations table * @param array $entries - * @throws DatabaseException + * @throws DiException|ReflectionException */ private function removeMigratedEntries(array $entries): void { diff --git a/src/Model/DbModel.php b/src/Model/DbModel.php index 49820ba7..300c3a50 100644 --- a/src/Model/DbModel.php +++ b/src/Model/DbModel.php @@ -214,7 +214,7 @@ public function hydrateFromOrm(array $data): self /** * @param array $args * @return mixed - * @throws ModelException + * @throws ModelException|BaseException */ public function __call(string $method, array $args = []) { diff --git a/src/Model/Factories/ModelFactory.php b/src/Model/Factories/ModelFactory.php index 9db63b00..40d05fed 100644 --- a/src/Model/Factories/ModelFactory.php +++ b/src/Model/Factories/ModelFactory.php @@ -18,9 +18,12 @@ use Quantum\Database\Contracts\DbalInterface; use Quantum\Model\Exceptions\ModelException; +use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Database\Database; use Quantum\Model\DbModel; use Quantum\Model\Model; +use ReflectionException; use Quantum\Di\Di; /** @@ -34,7 +37,7 @@ class ModelFactory * @template T of Model * @param class-string $modelClass * @return T - * @throws ModelException + * @throws ModelException|BaseException|ReflectionException */ public static function get(string $modelClass): Model { @@ -67,7 +70,18 @@ public static function get(string $modelClass): Model * Creates anonymous dynamic model * @param array $foreignKeys * @param array $hidden + * @return DbModel|BaseException */ + + /** + * @param string $table + * @param string $modelName + * @param string $idColumn + * @param array $foreignKeys + * @param array $hidden + * @return DbModel + * @throws DiException|BaseException|ReflectionException + */ public static function createDynamicModel( string $table, string $modelName = '@anonymous', @@ -93,6 +107,7 @@ public static function createDynamicModel( /** * @param array $foreignKeys * @param array $hidden + * @throws DiException|BaseException|ReflectionException */ protected static function createOrmInstance( string $table, diff --git a/src/Model/Helpers/model.php b/src/Model/Helpers/model.php index 9341f90c..4d7ed468 100644 --- a/src/Model/Helpers/model.php +++ b/src/Model/Helpers/model.php @@ -14,6 +14,7 @@ use Quantum\Database\Contracts\DbalInterface; use Quantum\Model\Exceptions\ModelException; +use Quantum\App\Exceptions\BaseException; use Quantum\Model\Factories\ModelFactory; use Quantum\Model\DbModel; use Quantum\Model\Model; @@ -22,7 +23,7 @@ * Gets the model instance * @param class-string $modelClass * @return T - * @throws ModelException + * @throws ModelException|BaseException|ReflectionException * @template T of Model */ function model(string $modelClass): Model @@ -53,7 +54,7 @@ function dynamicModel( /** * Wraps the orm instance into model - * @throws ModelException + * @throws ModelException|BaseException */ function wrapToModel(?DbalInterface $ormInstance, string $modelClass): ?DbModel { diff --git a/src/Model/Traits/SoftDeletes.php b/src/Model/Traits/SoftDeletes.php index ee1ee270..afacfc6e 100644 --- a/src/Model/Traits/SoftDeletes.php +++ b/src/Model/Traits/SoftDeletes.php @@ -132,8 +132,8 @@ public function findOne(int $id): ?DbModel /** * Find one record by column and value, excluding soft deleted unless withTrashed() is called. - * @param $value - * @throws BaseException + * @param mixed $value + * @throws ModelException|BaseException */ public function findOneBy(string $column, $value): ?DbModel { diff --git a/src/Module/ModuleLoader.php b/src/Module/ModuleLoader.php index 86978959..fcb839ab 100644 --- a/src/Module/ModuleLoader.php +++ b/src/Module/ModuleLoader.php @@ -50,10 +50,7 @@ class ModuleLoader private FileSystem $fs; /** - * @throws BaseException - * @throws DiException - * @throws ConfigException - * @throws ReflectionException|ModuleException + * @throws ModuleException|ConfigException|DiException|BaseException|ReflectionException */ public function __construct() { @@ -63,8 +60,7 @@ public function __construct() /** * @return array - * @throws ModuleException - * @throws RouteException + * @throws ModuleException|RouteException */ public function loadModulesRoutes(): array { @@ -86,8 +82,7 @@ public function loadModulesRoutes(): array } /** - * @throws ModuleException - * @throws RouteException + * @throws ModuleException|RouteException */ private function getModuleRouteDefinitions(string $module): Closure { diff --git a/src/Module/ModuleManager.php b/src/Module/ModuleManager.php index a76b8603..c28cec60 100644 --- a/src/Module/ModuleManager.php +++ b/src/Module/ModuleManager.php @@ -54,10 +54,7 @@ class ModuleManager private string $modulesConfigPath; /** - * @throws BaseException - * @throws DiException - * @throws ConfigException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function __construct(string $moduleName, string $template, bool $enabled, bool $withAssets = false) { @@ -81,9 +78,7 @@ public function getModuleName(): string } /** - * @throws ModuleException - * @throws ExceptionInterface - * @throws Exception + * @throws ModuleException|ExceptionInterface|Exception */ public function addModuleConfig(): void { diff --git a/src/Paginator/Adapters/ArrayPaginator.php b/src/Paginator/Adapters/ArrayPaginator.php index 38f5b411..43725791 100644 --- a/src/Paginator/Adapters/ArrayPaginator.php +++ b/src/Paginator/Adapters/ArrayPaginator.php @@ -18,6 +18,8 @@ use Quantum\Paginator\Contracts\PaginatorInterface; use Quantum\Paginator\Traits\PaginatorTrait; +use Quantum\Di\Exceptions\DiException; +use ReflectionException; /** * Class ArrayPaginator @@ -34,6 +36,7 @@ class ArrayPaginator implements PaginatorInterface /** * @param array $items + * @throws DiException|ReflectionException */ public function __construct(array $items, int $perPage, int $page = 1) { diff --git a/src/Paginator/Adapters/ModelPaginator.php b/src/Paginator/Adapters/ModelPaginator.php index 59bf30e8..060b33df 100644 --- a/src/Paginator/Adapters/ModelPaginator.php +++ b/src/Paginator/Adapters/ModelPaginator.php @@ -16,12 +16,14 @@ namespace Quantum\Paginator\Adapters; +use Quantum\Di\Exceptions\DiException; use Quantum\Paginator\Contracts\PaginatorInterface; use Quantum\Paginator\Traits\PaginatorTrait; use Quantum\App\Exceptions\BaseException; use Quantum\Model\ModelCollection; use Quantum\Model\DbModel; use Quantum\Model\Model; +use ReflectionException; /** * Class ModelPaginator @@ -35,6 +37,9 @@ class ModelPaginator implements PaginatorInterface private DbModel $model; + /** + * @throws DiException|ReflectionException + */ public function __construct(DbModel $model, int $perPage, int $page = 1) { $this->initialize($perPage, $page); diff --git a/src/Paginator/Traits/PaginatorTrait.php b/src/Paginator/Traits/PaginatorTrait.php index 00dd0c0b..85a5eb15 100644 --- a/src/Paginator/Traits/PaginatorTrait.php +++ b/src/Paginator/Traits/PaginatorTrait.php @@ -18,6 +18,7 @@ use Quantum\Config\Exceptions\ConfigException; use Quantum\Lang\Exceptions\LangException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Paginator\Enums\Pagination; use Quantum\Di\Exceptions\DiException; use ReflectionException; @@ -105,6 +106,7 @@ public function lastPageNumber(): int /** * Get the current page link + * @throws DiException|ReflectionException */ public function currentPageLink(bool $withBaseUrl = false): ?string { @@ -113,6 +115,7 @@ public function currentPageLink(bool $withBaseUrl = false): ?string /** * Get first page link + * @throws DiException|ReflectionException */ public function firstPageLink(bool $withBaseUrl = false): ?string { @@ -121,6 +124,7 @@ public function firstPageLink(bool $withBaseUrl = false): ?string /** * Get previous page link + * @throws DiException|ReflectionException */ public function previousPageLink(bool $withBaseUrl = false): ?string { @@ -129,6 +133,7 @@ public function previousPageLink(bool $withBaseUrl = false): ?string /** * Get next page link + * @throws DiException|ReflectionException */ public function nextPageLink(bool $withBaseUrl = false): ?string { @@ -137,6 +142,7 @@ public function nextPageLink(bool $withBaseUrl = false): ?string /** * Get last page link + * @throws DiException|ReflectionException */ public function lastPageLink(bool $withBaseUrl = false): ?string { @@ -154,6 +160,7 @@ public function perPage(): int /** * Get all page links * @return array + * @throws DiException|ReflectionException */ public function links(bool $withBaseUrl = false): array { @@ -240,7 +247,7 @@ protected function getPageLink(?int $pageNumber, bool $withBaseUrl = false): ?st /** * Get next page item HTML - * @throws DiException|ReflectionException|ConfigException|LangException + * @throws LoaderException|ConfigException|DiException|ReflectionException|LangException */ protected function getNextPageItem(?string $nextPageLink): string { @@ -260,7 +267,7 @@ protected function getNextPageItem(?string $nextPageLink): string */ /** * Get previous page item HTML - * @throws ConfigException|DiException|LangException|ReflectionException + * @throws LoaderException|ConfigException|DiException|LangException|ReflectionException */ protected function getPreviousPageItem(?string $previousPageLink): string { diff --git a/src/Renderer/Adapters/HtmlAdapter.php b/src/Renderer/Adapters/HtmlAdapter.php index 1b1082c1..6f20982d 100644 --- a/src/Renderer/Adapters/HtmlAdapter.php +++ b/src/Renderer/Adapters/HtmlAdapter.php @@ -40,10 +40,7 @@ class HtmlAdapter implements TemplateRendererInterface /** * @param array|null $configs - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ConfigException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function __construct(?array $configs = []) { diff --git a/src/Renderer/Adapters/TwigAdapter.php b/src/Renderer/Adapters/TwigAdapter.php index 4a5a52ba..5c18dc0c 100644 --- a/src/Renderer/Adapters/TwigAdapter.php +++ b/src/Renderer/Adapters/TwigAdapter.php @@ -46,10 +46,7 @@ class TwigAdapter implements TemplateRendererInterface /** * @param array|null $configs - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ConfigException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function __construct(?array $configs = []) { @@ -61,7 +58,7 @@ public function __construct(?array $configs = []) /** * Renders the view * @param array $params - * @throws DiException|LoaderError|ReflectionException|RendererException|RuntimeError|SyntaxError + * @throws RendererException|LoaderError|DiException|BaseException|ReflectionException|RuntimeError|SyntaxError */ public function render(string $view, array $params = []): string { @@ -75,7 +72,7 @@ public function render(string $view, array $params = []): string } /** - * @throws RendererException|DiException|ReflectionException + * @throws RendererException|DiException|BaseException|ReflectionException */ private function getLoader(string $view): FilesystemLoader { diff --git a/src/Renderer/Factories/RendererFactory.php b/src/Renderer/Factories/RendererFactory.php index 5129ba50..10df8b72 100644 --- a/src/Renderer/Factories/RendererFactory.php +++ b/src/Renderer/Factories/RendererFactory.php @@ -80,7 +80,7 @@ public function resolve(?string $adapter = null): Renderer } /** - * @throws RendererException + * @throws RendererException|BaseException|ReflectionException */ private function createInstance(string $adapterClass, string $adapter): Renderer { diff --git a/src/ResourceCache/ViewCache.php b/src/ResourceCache/ViewCache.php index 5e8a0351..41fdb961 100644 --- a/src/ResourceCache/ViewCache.php +++ b/src/ResourceCache/ViewCache.php @@ -136,10 +136,7 @@ public function delete(string $key): void } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function exists(string $key): bool { @@ -194,10 +191,7 @@ private function getCacheDir(): string } /** - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws BaseException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private function getCacheFile(string $key): string { diff --git a/src/Session/Adapters/Database/DatabaseSessionAdapter.php b/src/Session/Adapters/Database/DatabaseSessionAdapter.php index cc268df0..22482bc4 100644 --- a/src/Session/Adapters/Database/DatabaseSessionAdapter.php +++ b/src/Session/Adapters/Database/DatabaseSessionAdapter.php @@ -16,11 +16,14 @@ namespace Quantum\Session\Adapters\Database; -use Quantum\Model\Exceptions\ModelException; use Quantum\Session\Contracts\SessionStorageInterface; use Quantum\Session\Exceptions\SessionException; +use Quantum\Model\Exceptions\ModelException; use Quantum\Model\Factories\ModelFactory; +use Quantum\App\Exceptions\BaseException; use Quantum\Session\Traits\SessionTrait; +use Quantum\Di\Exceptions\DiException; +use ReflectionException; /** * Class Session @@ -49,7 +52,7 @@ class DatabaseSessionAdapter implements SessionStorageInterface /** * @param array|null $params - * @throws SessionException|ModelException + * @throws SessionException|ModelException|BaseException|DiException|ReflectionException */ public function __construct(?array $params = null) { @@ -58,7 +61,7 @@ public function __construct(?array $params = null) /** * @param array|null $params - * @throws SessionException|ModelException + * @throws SessionException|ModelException|BaseException|DiException|ReflectionException */ protected function initializeSession(?array $params = null): void { diff --git a/src/Session/Factories/SessionFactory.php b/src/Session/Factories/SessionFactory.php index 599b2579..ec7d257d 100644 --- a/src/Session/Factories/SessionFactory.php +++ b/src/Session/Factories/SessionFactory.php @@ -46,10 +46,7 @@ class SessionFactory private array $instances = []; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function get(?string $adapter = null): Session { @@ -57,10 +54,7 @@ public static function get(?string $adapter = null): Session } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function resolve(?string $adapter = null): Session { @@ -80,7 +74,7 @@ public function resolve(?string $adapter = null): Session } /** - * @throws SessionException + * @throws BaseException|DiException|ReflectionException */ private function createInstance(string $adapterClass, string $adapter): Session { diff --git a/src/Session/Helpers/session.php b/src/Session/Helpers/session.php index d1318931..87b56162 100644 --- a/src/Session/Helpers/session.php +++ b/src/Session/Helpers/session.php @@ -19,10 +19,7 @@ use Quantum\Session\Session; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ function session(?string $adapter = null): Session { diff --git a/src/Session/Traits/SessionTrait.php b/src/Session/Traits/SessionTrait.php index 789e7933..23958199 100644 --- a/src/Session/Traits/SessionTrait.php +++ b/src/Session/Traits/SessionTrait.php @@ -16,8 +16,11 @@ namespace Quantum\Session\Traits; +use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Session\Exceptions\SessionException; use Quantum\Model\Exceptions\ModelException; +use ReflectionException; /** * Traits SessionTrait @@ -28,6 +31,7 @@ trait SessionTrait /** * @inheritDoc * @return array + * @throws BaseException|ReflectionException */ public function all(): array { @@ -120,7 +124,7 @@ public function getId(): ?string /** * @inheritDoc - * @throws SessionException|ModelException + * @throws SessionException|ModelException|DiException|BaseException|ReflectionException */ public function regenerateId(): bool { diff --git a/src/Storage/Contracts/TokenServiceInterface.php b/src/Storage/Contracts/TokenServiceInterface.php index f17a5329..34626bec 100644 --- a/src/Storage/Contracts/TokenServiceInterface.php +++ b/src/Storage/Contracts/TokenServiceInterface.php @@ -27,5 +27,4 @@ public function getAccessToken(): string; public function getRefreshToken(): string; public function saveTokens(string $accessToken, ?string $refreshToken = null): bool; - } diff --git a/src/Storage/Factories/FileSystemFactory.php b/src/Storage/Factories/FileSystemFactory.php index 9e652d54..74668989 100644 --- a/src/Storage/Factories/FileSystemFactory.php +++ b/src/Storage/Factories/FileSystemFactory.php @@ -60,10 +60,7 @@ class FileSystemFactory private array $instances = []; /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ConfigException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public static function get(?string $adapter = null): FileSystem { @@ -71,10 +68,7 @@ public static function get(?string $adapter = null): FileSystem } /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ConfigException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function resolve(?string $adapter = null): FileSystem { @@ -94,10 +88,7 @@ public function resolve(?string $adapter = null): FileSystem } /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private function createInstance(string $adapterClass, string $adapter): FileSystem { @@ -123,10 +114,7 @@ private function getAdapterClass(string $adapter): string } /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws ConfigException|DiException|BaseException|ReflectionException */ private function createCloudApp(string $adapter): ?CloudAppInterface { @@ -145,10 +133,7 @@ private function createCloudApp(string $adapter): ?CloudAppInterface } /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - * @throws ServiceException + * @throws ServiceException|DiException|BaseException|ReflectionException */ private function createTokenService(string $adapter): TokenServiceInterface { diff --git a/src/Storage/Helpers/fs.php b/src/Storage/Helpers/fs.php index e7d602b9..16a9cc90 100644 --- a/src/Storage/Helpers/fs.php +++ b/src/Storage/Helpers/fs.php @@ -20,10 +20,7 @@ /** * Gets the FileSystem handler - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function fs(?string $adapter = null): FileSystem { diff --git a/src/Storage/Traits/CloudAppTrait.php b/src/Storage/Traits/CloudAppTrait.php index 33635f6a..5a4c6d66 100644 --- a/src/Storage/Traits/CloudAppTrait.php +++ b/src/Storage/Traits/CloudAppTrait.php @@ -30,9 +30,7 @@ trait CloudAppTrait * @inheritDoc * @param array|string|null $data * @param array $headers - * @throws BaseException - * @throws HttpException - * @throws Exception + * @throws HttpException|BaseException|Exception */ public function sendRequest(string $uri, $data = null, array $headers = [], string $method = 'POST') { diff --git a/src/Storage/UploadedFile.php b/src/Storage/UploadedFile.php index b8313c6a..316540d0 100644 --- a/src/Storage/UploadedFile.php +++ b/src/Storage/UploadedFile.php @@ -20,7 +20,7 @@ use Quantum\Storage\Contracts\FilesystemAdapterInterface; use Quantum\Storage\Exceptions\FileSystemException; use Quantum\Storage\Exceptions\FileUploadException; -use Quantum\Environment\Exceptions\EnvException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Config\Exceptions\ConfigException; use Quantum\Lang\Exceptions\LangException; use Quantum\App\Exceptions\BaseException; @@ -115,10 +115,7 @@ class UploadedFile extends SplFileInfo /** * @param array $meta - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ public function __construct(array $meta) { @@ -268,11 +265,10 @@ public function getDimensions(): array /** * Save the uploaded file - * @throws BaseException - * @throws FileSystemException - * @throws FileUploadException - * @throws ImageResizeException - * @throws EnvException + * @param string $dest + * @param bool $overwrite + * @return bool + * @throws FileUploadException|FileSystemException|ImageResizeException|BaseException */ public function save(string $dest, bool $overwrite = false): bool { @@ -318,9 +314,7 @@ public function save(string $dest, bool $overwrite = false): bool /** * Sets modification function on image * @param array $params - * @throws BaseException - * @throws FileUploadException - * @throws LangException + * @throws FileUploadException|LangException|BaseException */ public function modify(string $funcName, array $params): UploadedFile { @@ -364,9 +358,8 @@ public function isUploaded(): bool /** * Checks if the given file is image - * @param string $filePath */ - public function isImage($filePath): bool + public function isImage(string $filePath): bool { return (bool) getimagesize($filePath); } @@ -399,10 +392,7 @@ protected function allowed(string $extension, string $mimeType): bool /** * Loads allowed mime types from config (shared/config/uploads.php) if present. - * @throws ConfigException - * @throws DiException - * @throws FileUploadException - * @throws ReflectionException + * @throws FileUploadException|LoaderException|ConfigException|DiException|ReflectionException */ protected function loadAllowedMimeTypesFromConfig(): void { diff --git a/src/Tracer/ErrorHandler.php b/src/Tracer/ErrorHandler.php index 3decf9ec..16ae139c 100644 --- a/src/Tracer/ErrorHandler.php +++ b/src/Tracer/ErrorHandler.php @@ -90,12 +90,7 @@ public function handleError(int $severity, string $message, string $file, int $l } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws RendererException - * @throws DebugBarException + * @throws ConfigException|RendererException|DebugBarException|DiException|BaseException|ReflectionException */ public function handleException(Throwable $throwable): void { @@ -113,11 +108,7 @@ private function handleCliException(Throwable $throwable): void } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws DebugBarException + * @throws ConfigException|RendererException|DiException|BaseException|ReflectionException */ private function handleWebException(Throwable $throwable): void { @@ -153,10 +144,7 @@ private function logError(Throwable $e, string $errorType): void /** * Composes the stack trace * @return array - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|RendererException|DiException|BaseException|ReflectionException */ private function composeStackTrace(Throwable $e): array { @@ -183,10 +171,7 @@ private function composeStackTrace(Throwable $e): array /** * Gets the source code where the error happens - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|RendererException|DiException|BaseException|ReflectionException */ private function getSourceCode(string $filename, int $lineNumber, string $className): string { diff --git a/src/Validation/Traits/General.php b/src/Validation/Traits/General.php index 41d59e9c..46de926d 100644 --- a/src/Validation/Traits/General.php +++ b/src/Validation/Traits/General.php @@ -211,8 +211,7 @@ protected function same($value, string $otherField): bool /** * Validates uniqueness * @param mixed $value - * @throws BaseException - * @throws ModelException + * @throws ModelException|BaseException|ReflectionException */ protected function unique($value, string $className, string $columnName): bool { @@ -227,8 +226,7 @@ protected function unique($value, string $className, string $columnName): bool /** * Validates record existence * @param mixed $value - * @throws BaseException - * @throws ModelException + * @throws ModelException|BaseException|ReflectionException */ protected function exists($value, string $className, string $columnName): bool { @@ -243,10 +241,7 @@ protected function exists($value, string $className, string $columnName): bool /** * Check Captcha * @param mixed $value - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ protected function captcha($value): bool { diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php index 94df7b62..dc23e0b9 100644 --- a/src/Validation/Validator.php +++ b/src/Validation/Validator.php @@ -18,6 +18,7 @@ use Quantum\Config\Exceptions\ConfigException; use Quantum\Lang\Exceptions\LangException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Validation\Traits\Resource; use Quantum\Di\Exceptions\DiException; use Quantum\Validation\Traits\General; @@ -194,10 +195,7 @@ public function addRule(string $rule, Closure $function): void /** * Gets validation errors with translations * @return array> - * @throws ConfigException - * @throws DiException - * @throws LangException - * @throws ReflectionException + * @throws ConfigException|LoaderException|LangException|DiException|ReflectionException */ public function getErrors(): array { diff --git a/src/View/Helpers/view.php b/src/View/Helpers/view.php index 25f5d15f..f1b92567 100644 --- a/src/View/Helpers/view.php +++ b/src/View/Helpers/view.php @@ -28,11 +28,7 @@ /** * Rendered view - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException - * @throws ViewException + * @throws ViewException|ConfigException|DiException|BaseException|ReflectionException */ function view(): ?string { @@ -42,10 +38,7 @@ function view(): ?string /** * Rendered partial * @param array $args - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|DiException|BaseException|ReflectionException */ function partial(string $partial, array $args = []): string { diff --git a/src/View/QtView.php b/src/View/QtView.php index 03062677..0af6a736 100644 --- a/src/View/QtView.php +++ b/src/View/QtView.php @@ -283,8 +283,7 @@ private function sanitizeHtml(string $value): string } /** - * @throws ReflectionException - * @throws DiException + * @throws ReflectionException|DiException */ private function updateDebugger(string $viewFile): void { From 2fd77b29ed70d6150b6a480140d66dab53c75dfe Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:43:38 +0400 Subject: [PATCH 43/77] [#373] Move SetupErrorHandlerStage into boot pipeline for both adapters Made-with: Cursor --- src/App/Adapters/ConsoleAppAdapter.php | 3 +-- src/App/Adapters/WebAppAdapter.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index 10fb21c0..48bf89af 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -64,6 +64,7 @@ public function __construct() if ($commandName !== 'core:env') { $stages[] = new LoadEnvironmentStage(); $stages[] = new LoadAppConfigStage(); + $stages[] = new SetupErrorHandlerStage(); } $pipeline = new BootPipeline($stages); @@ -86,8 +87,6 @@ public function start(): ?int $this->registerCoreCommands(); $this->registerAppCommands(); - (new SetupErrorHandlerStage())->process($this->context); - $this->validateCommand(); $exitCode = $this->application->run($this->input, $this->output); diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 57cf17f7..49a4f772 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -80,6 +80,7 @@ public function __construct() new LoadHelpersStage(), new LoadEnvironmentStage(), new LoadAppConfigStage(), + new SetupErrorHandlerStage(), ]); $pipeline->run($this->context); @@ -113,7 +114,6 @@ public function start(): ?int stop(); } - (new SetupErrorHandlerStage())->process($this->context); $this->initializeDebugger(); $moduleLoader = Di::get(ModuleLoader::class); From 97362a5a877b043a4363f39d14e41397759869b7 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:33:16 +0400 Subject: [PATCH 44/77] [#454] Merge HttpRequest/HttpResponse into Request/Response, remove facade layer Made-with: Cursor --- src/Http/Request.php | 278 +++++++++++++++--- src/Http/Request/HttpRequest.php | 265 ----------------- src/Http/Response.php | 98 +++--- src/Http/Response/HttpResponse.php | 98 ------ tests/Unit/Http/RequestTest.php | 3 +- .../Request/HttpRequestInternalTest.php | 3 +- 6 files changed, 295 insertions(+), 450 deletions(-) delete mode 100644 src/Http/Request/HttpRequest.php delete mode 100644 src/Http/Response/HttpResponse.php diff --git a/src/Http/Request.php b/src/Http/Request.php index cd10eb0c..8b4b19b7 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -16,68 +16,250 @@ namespace Quantum\Http; -use Quantum\Http\Request\HttpRequest; +use Quantum\Config\Exceptions\ConfigException; +use Quantum\Http\Exceptions\HttpException; +use Quantum\App\Exceptions\BaseException; +use Quantum\Http\Traits\Request\RawInput; +use Quantum\Http\Traits\Request\Internal; +use Quantum\Http\Traits\Request\Header; +use Quantum\Http\Traits\Request\Params; +use Quantum\Di\Exceptions\DiException; +use Quantum\Http\Traits\Request\Query; +use Quantum\Http\Traits\Request\Route; +use Quantum\Http\Traits\Request\Body; +use Quantum\Http\Traits\Request\File; +use Quantum\Http\Traits\Request\Url; +use Quantum\Environment\Server; +use ReflectionException; +use Quantum\Csrf\Csrf; /** * Class Request * @package Quantum\Http - * @method static void create(string $method, string $url, array $params = [], array $headers = [], array $file = null) - * @method static void flush() - * @method static string|null getMethod() - * @method static void setMethod(string $method) - * @method static bool isMethod(string $method) - * @method static string getProtocol() - * @method static void setProtocol($protocol) - * @method static string getHost() - * @method static void setHost($host) - * @method static string getPort() - * @method static void setPort($port) - * @method static string|null getUri() - * @method static void setUri($uri) - * @method static string getQuery() - * @method static string|null getQueryParam(string $key) - * @method static void setQueryParam(string $key, string $value) - * @method static void setQuery($query) - * @method static bool has(string $key) - * @method static mixed get(string $key, string $default = null, bool $raw = false) - * @method static void set(string $key, $value) - * @method static array all() - * @method static void delete(string $key) - * @method static bool hasFile(string $key) - * @method static mixed getFile(string $key) - * @method static bool hasHeader(string $key) - * @method static string|null getHeader(string $key) - * @method static void setHeader(string $key, $value) - * @method static array allHeaders() - * @method static void deleteHeader(string $key) - * @method static string|null getSegment(int $number) - * @method static array getAllSegments() - * @method static string|null getCsrfToken() - * @method static string|null getAuthorizationBearer() - * @method static array|null getBasicAuthCredentials() - * @method static bool isAjax() - * @method static string|null getReferrer() - * @mixin HttpRequest */ class Request { + use Route; + use Header; + use Body; + use Url; + use Query; + use Params; + use File; + use RawInput; + use Internal; + + /** + * Available methods + */ + public const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; + + /** + * Default port for HTTP + */ + public const DEFAULT_HTTP_PORT = 80; + + /** + * Default port for HTTPS + */ + public const DEFAULT_HTTPS_PORT = 443; + + /** + * Request method + */ + private static ?string $__method = null; + + protected static Server $server; + + private static bool $initialized = false; + + /** + * Initializes the request properties using the server instance. + * @throws ConfigException|DiException|BaseException|ReflectionException + */ + public static function init(Server $server): void + { + if (self::$initialized) { + return; + } + + self::flush(); + + self::$server = $server; + + self::setServerInfo(); + self::setContentType(); + self::setRequestHeaders(); + + ['params' => $rawInputParams, 'files' => $rawInputFiles] = self::getRawInputParams(); + + self::setRequestParams($rawInputParams); + self::setUploadedFiles($rawInputFiles); + + self::$initialized = true; + } + + /** + * Flushes the request header, body and files + */ + public static function flush(): void + { + self::$__headers = []; + self::$__request = []; + self::$__files = []; + self::$__protocol = null; + self::$__host = null; + self::$__port = null; + self::$__uri = null; + self::$__query = null; + + self::$initialized = false; + } + + /** + * Sets the merged request parameters + * @param array $params + */ + public static function setRequestParams(array $params): void + { + self::$__request = array_merge( + self::getParams(), + self::postParams(), + self::jsonPayloadParams(), + self::urlEncodedParams(), + $params + ); + } + + /** + * Sets the uploaded files array merging handled $_FILES and parsed files + * @param array $files + * @throws BaseException + * @throws ReflectionException + */ + public static function setUploadedFiles(array $files): void + { + self::$__files = array_merge( + self::handleFiles($_FILES), + $files + ); + } + + /** + * Gets the request method + */ + public static function getMethod(): ?string + { + return self::$__method; + } + + /** + * Sets the request method + * @throws BaseException + */ + public static function setMethod(string $method): void + { + if (!in_array(strtoupper($method), self::METHODS)) { + throw HttpException::requestMethodNotAvailable($method); + } + + self::$__method = $method; + } + + /** + * Checks if the current method matches the given method + */ + public static function isMethod(string $method): bool + { + return strcasecmp($method, self::$__method ?? '') === 0; + } + + /** + * Gets Cross Site Request Forgery Token + */ + public static function getCsrfToken(): ?string + { + $csrfToken = null; + + if (self::has(Csrf::TOKEN_KEY)) { + $csrfToken = (string) self::get(Csrf::TOKEN_KEY); + } elseif (self::hasHeader('X-' . Csrf::TOKEN_KEY)) { + $csrfToken = self::getHeader('X-' . Csrf::TOKEN_KEY); + } + + return $csrfToken; + } + + /** + * Gets the base url + * @throws DiException|ReflectionException + */ + public static function getBaseUrl(bool $withModulePrefix = false): string + { + $baseUrl = config()->get('app.base_url'); + + $prefix = route_prefix(); + $modulePrefix = ($withModulePrefix && !in_array($prefix, [null, '', '0'], true)) ? '/' . $prefix : ''; + + if ($baseUrl) { + return $baseUrl . $modulePrefix; + } + + return self::getHostPrefix() . $modulePrefix; + } + + /** + * Gets the current url + */ + public static function getCurrentUrl(): string + { + $uri = self::getUri(); + $query = self::getQuery(); + $queryPart = $query ? '?' . $query : ''; + + return self::getHostPrefix() . '/' . $uri . $queryPart; + } + + /** + * Gets the protocol, host, and optional port part of the URL. + */ + private static function getHostPrefix(): string + { + $protocol = self::getProtocol(); + $host = self::getHost(); + $port = self::getPort(); + + $defaultPort = $protocol === 'https' ? self::DEFAULT_HTTPS_PORT : self::DEFAULT_HTTP_PORT; + + $portPart = ($port && $port != $defaultPort) ? ':' . $port : ''; + + return $protocol . '://' . $host . $portPart; + } + + /** + * Sets server data (method, protocol, host, port, uri, query). + */ + private static function setServerInfo(): void + { + foreach (['method', 'protocol', 'host', 'port', 'uri', 'query'] as $name) { + self::${"__{$name}"} = self::$server->$name(); + } + } + /** - * @param string $function The function name - * @param array $arguments - * @return mixed + * Sets the normalized request content type. */ - public function __call(string $function, array $arguments) + private static function setContentType(): void { - return HttpRequest::$function(...$arguments); + self::$__contentType = self::$server->contentType(true); } /** - * @param string $function The function name - * @param array $arguments - * @return mixed + * Sets request headers, normalizing keys to lowercase. + * @throws DiException|ReflectionException */ - public static function __callStatic(string $function, array $arguments) + private static function setRequestHeaders(): void { - return HttpRequest::$function(...$arguments); + self::$__headers = array_change_key_case(getallheaders()); } } diff --git a/src/Http/Request/HttpRequest.php b/src/Http/Request/HttpRequest.php deleted file mode 100644 index fc9490ca..00000000 --- a/src/Http/Request/HttpRequest.php +++ /dev/null @@ -1,265 +0,0 @@ - - * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) - * @link http://quantum.softberg.org/ - * @since 3.0.0 - */ - -namespace Quantum\Http\Request; - -use Quantum\Config\Exceptions\ConfigException; -use Quantum\Http\Exceptions\HttpException; -use Quantum\App\Exceptions\BaseException; -use Quantum\Http\Traits\Request\RawInput; -use Quantum\Http\Traits\Request\Internal; -use Quantum\Http\Traits\Request\Header; -use Quantum\Http\Traits\Request\Params; -use Quantum\Di\Exceptions\DiException; -use Quantum\Http\Traits\Request\Query; -use Quantum\Http\Traits\Request\Route; -use Quantum\Http\Traits\Request\Body; -use Quantum\Http\Traits\Request\File; -use Quantum\Http\Traits\Request\Url; -use Quantum\Environment\Server; -use ReflectionException; -use Quantum\Csrf\Csrf; - -/** - * Class HttpRequest - * @package Quantum\Http - */ -abstract class HttpRequest -{ - use Route; - use Header; - use Body; - use Url; - use Query; - use Params; - use File; - use RawInput; - use Internal; - - /** - * Available methods - */ - public const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; - - /** - * Default port for HTTP - */ - public const DEFAULT_HTTP_PORT = 80; - - /** - * Default port for HTTPS - */ - public const DEFAULT_HTTPS_PORT = 443; - - /** - * Request method - */ - private static ?string $__method = null; - - protected static Server $server; - - private static bool $initialized = false; - - /** - * Initializes the request static properties using the server instance. - * @throws ConfigException|DiException|BaseException|ReflectionException - */ - public static function init(Server $server): void - { - if (self::$initialized) { - return; - } - - self::flush(); - - self::$server = $server; - - self::setServerInfo(); - self::setContentType(); - self::setRequestHeaders(); - - ['params' => $rawInputParams, 'files' => $rawInputFiles] = self::getRawInputParams(); - - self::setRequestParams($rawInputParams); - self::setUploadedFiles($rawInputFiles); - - self::$initialized = true; - } - - /** - * Flushes the request header , body and files - */ - public static function flush(): void - { - self::$__headers = []; - self::$__request = []; - self::$__files = []; - self::$__protocol = null; - self::$__host = null; - self::$__port = null; - self::$__uri = null; - self::$__query = null; - - self::$initialized = false; - } - - /** - * Sets the merged request parameters - * @param array $params - */ - public static function setRequestParams(array $params): void - { - self::$__request = array_merge( - self::getParams(), - self::postParams(), - self::jsonPayloadParams(), - self::urlEncodedParams(), - $params - ); - } - - /** - * Sets the uploaded files array merging handled $_FILES and parsed files - * @param array $files - * @throws BaseException - * @throws ReflectionException - */ - public static function setUploadedFiles(array $files): void - { - self::$__files = array_merge( - self::handleFiles($_FILES), - $files - ); - } - - /** - * Gets the request method - */ - public static function getMethod(): ?string - { - return self::$__method; - } - - /** - * Sets the request method - * @throws BaseException - */ - public static function setMethod(string $method): void - { - if (!in_array(strtoupper($method), self::METHODS)) { - throw HttpException::requestMethodNotAvailable($method); - } - - self::$__method = $method; - } - - /** - * Checks if the current method matches the given method - */ - public static function isMethod(string $method): bool - { - return strcasecmp($method, self::$__method ?? '') === 0; - } - - /** - * Gets Cross Site Request Forgery Token - */ - public static function getCsrfToken(): ?string - { - $csrfToken = null; - - if (self::has(Csrf::TOKEN_KEY)) { - $csrfToken = (string) self::get(Csrf::TOKEN_KEY); - } elseif (self::hasHeader('X-' . Csrf::TOKEN_KEY)) { - $csrfToken = self::getHeader('X-' . Csrf::TOKEN_KEY); - } - - return $csrfToken; - } - - /** - * Gets the base url - * @throws DiException|ReflectionException - */ - public static function getBaseUrl(bool $withModulePrefix = false): string - { - $baseUrl = config()->get('app.base_url'); - - $prefix = route_prefix(); - $modulePrefix = ($withModulePrefix && !in_array($prefix, [null, '', '0'], true)) ? '/' . $prefix : ''; - - if ($baseUrl) { - return $baseUrl . $modulePrefix; - } - - return self::getHostPrefix() . $modulePrefix; - } - - /** - * Gets the current url - */ - public static function getCurrentUrl(): string - { - $uri = self::getUri(); - $query = self::getQuery(); - $queryPart = $query ? '?' . $query : ''; - - return self::getHostPrefix() . '/' . $uri . $queryPart; - } - - /** - * Gets the protocol, host, and optional port part of the URL. - */ - private static function getHostPrefix(): string - { - $protocol = self::getProtocol(); - $host = self::getHost(); - $port = self::getPort(); - - $defaultPort = $protocol === 'https' ? self::DEFAULT_HTTPS_PORT : self::DEFAULT_HTTP_PORT; - - $portPart = ($port && $port != $defaultPort) ? ':' . $port : ''; - - return $protocol . '://' . $host . $portPart; - } - - /** - * Sets server data (method, protocol, host, port, uri, query). - */ - private static function setServerInfo(): void - { - foreach (['method', 'protocol', 'host', 'port', 'uri', 'query'] as $name) { - self::${"__{$name}"} = self::$server->$name(); - } - } - - /** - * Sets the normalized request content type. - */ - private static function setContentType(): void - { - self::$__contentType = self::$server->contentType(true); - } - - /** - * Sets request headers, normalizing keys to lowercase. - * @throws DiException|ReflectionException - */ - private static function setRequestHeaders(): void - { - self::$__headers = array_change_key_case(getallheaders()); - } -} diff --git a/src/Http/Response.php b/src/Http/Response.php index 38bea3c9..25dbed7d 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -16,55 +16,83 @@ namespace Quantum\Http; -use Quantum\Http\Response\HttpResponse; +use Quantum\Http\Traits\Response\Header; +use Quantum\Http\Traits\Response\Status; +use Quantum\Http\Traits\Response\Body; +use Quantum\Environment\Environment; +use Quantum\Environment\Enums\Env; +use Quantum\Http\Enums\StatusCode; +use Exception; /** * Class Response * @package Quantum\Http - * @method static void init() - * @method static void flush() - * @method static void send() - * @method static string getContent() - * @method static void setStatusCode(int $code) - * @method static int getStatusCode() - * @method static string getStatusText() - * @method static void redirect(string $url, int $code = null) - * @method static void json(array $data = null, int $code = null) - * @method static void xml(array $data = null, $root = '', int $code = null) - * @method static void html(string $html, int $code = null) - * @method static bool has(string $key) - * @method static mixed get(string $key, string $default = null) - * @method static void set(string $key, $value) - * @method static array all() - * @method static void delete(string $key) - * @method static bool hasHeader(string $key)) - * @method static string|null getHeader(string $key) - * @method static void setHeader(string $key, string $value) - * @method static array allHeaders() - * @method static void deleteHeader(string $key) - * @method static void setContentType(string $contentType) - * @method static string|null getContentType() - * @mixin HttpResponse */ class Response { + use Header; + use Body; + use Status; + + /** + * XML root element + * @var string + */ + private static string $xmlRoot = ''; + + /** + * Callback function + * @var string + */ + private static string $callbackFunction = ''; + + private static bool $initialized = false; + + /** + * Initialize the Response + */ + public static function init(): void + { + if (self::$initialized) { + return; + } + + self::flush(); + + self::$initialized = true; + } + /** - * @param string $function The function name - * @param array $arguments - * @return mixed + * Flushes the response header and body */ - public function __call(string $function, array $arguments) + public static function flush(): void { - return HttpResponse::$function(...$arguments); + self::$__statusCode = StatusCode::OK; + self::$__headers = []; + self::$__response = []; + self::$xmlRoot = ''; + self::$callbackFunction = ''; + self::$initialized = false; } /** - * @param string $function The function name - * @param array $arguments - * @return mixed + * Sends all response data to the client and finishes the request. + * @throws Exception */ - public static function __callStatic(string $function, array $arguments) + public static function send(): void { - return HttpResponse::$function(...$arguments); + if (Environment::getInstance()->getAppEnv() !== Env::TESTING) { + while (ob_get_level() > 0) { + ob_end_clean(); + } + } + + foreach (self::$__headers as $key => $value) { + header($key . ': ' . $value); + } + + http_response_code(self::getStatusCode()); + + echo self::getContent(); } } diff --git a/src/Http/Response/HttpResponse.php b/src/Http/Response/HttpResponse.php deleted file mode 100644 index 9de1f6e4..00000000 --- a/src/Http/Response/HttpResponse.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) - * @link http://quantum.softberg.org/ - * @since 3.0.0 - */ - -namespace Quantum\Http\Response; - -use Quantum\Http\Traits\Response\Header; -use Quantum\Http\Traits\Response\Status; -use Quantum\Http\Traits\Response\Body; -use Quantum\Environment\Environment; -use Quantum\Environment\Enums\Env; -use Quantum\Http\Enums\StatusCode; -use Exception; - -/** - * Class HttpResponse - * @package Quantum\Http\Response - */ -abstract class HttpResponse -{ - use Header; - use Body; - use Status; - - /** - * XML root element - * @var string - */ - private static string $xmlRoot = ''; - - /** - * Callback function - * @var string - */ - private static string $callbackFunction = ''; - - private static bool $initialized = false; - - /** - * Initialize the Response - */ - public static function init(): void - { - if (self::$initialized) { - return; - } - - self::flush(); - - self::$initialized = true; - } - - /** - * Flushes the response header and body - */ - public static function flush(): void - { - self::$__statusCode = StatusCode::OK; - self::$__headers = []; - self::$__response = []; - self::$xmlRoot = ''; - self::$callbackFunction = ''; - self::$initialized = false; - } - - /** - * Sends all response data to the client and finishes the request. - * @throws Exception - */ - public static function send(): void - { - if (Environment::getInstance()->getAppEnv() !== Env::TESTING) { - while (ob_get_level() > 0) { - ob_end_clean(); - } - } - - foreach (self::$__headers as $key => $value) { - header($key . ': ' . $value); - } - - http_response_code(self::getStatusCode()); - - echo self::getContent(); - } -} diff --git a/tests/Unit/Http/RequestTest.php b/tests/Unit/Http/RequestTest.php index f48e7384..e238a6d5 100644 --- a/tests/Unit/Http/RequestTest.php +++ b/tests/Unit/Http/RequestTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\Http; -use Quantum\Http\Request\HttpRequest; use Quantum\Tests\Unit\AppTestCase; use Quantum\Http\Request; @@ -16,7 +15,7 @@ public function setUp(): void public function tearDown(): void { - HttpRequest::flush(); + Request::flush(); } public function testSetGetMethod(): void diff --git a/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php b/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php index 2aad0217..cbc35399 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php @@ -2,7 +2,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Request; -use Quantum\Http\Request\HttpRequest; use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\UploadedFile; use Quantum\Http\Request; @@ -67,7 +66,7 @@ public function testRequestParamsAreSet(): void Request::create('POST', 'http://localhost/submit', $data); - $this->assertEquals('bar', HttpRequest::get('foo')); + $this->assertEquals('bar', Request::get('foo')); } public function testUploadedFilesAreSet(): void From a623729af04ab346635dd37a03942820a3f7395b Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:15:28 +0400 Subject: [PATCH 45/77] [#454] Convert Request/Response traits from static to instance-based, update all callers and tests --- src/App/Adapters/WebAppAdapter.php | 62 ++------ src/App/Stages/InitHttpStage.php | 41 ++++++ src/App/Traits/WebAppTrait.php | 12 -- src/Auth/Adapters/JwtAuthAdapter.php | 43 +++--- src/Http/Helpers/http.php | 39 ++++- src/Http/Request.php | 137 +++++++++--------- src/Http/Response.php | 41 ++---- src/Http/Traits/Request/Body.php | 26 ++-- src/Http/Traits/Request/File.php | 20 +-- src/Http/Traits/Request/Header.php | 58 ++++---- src/Http/Traits/Request/Internal.php | 15 +- src/Http/Traits/Request/Params.php | 32 ++-- src/Http/Traits/Request/Query.php | 22 +-- src/Http/Traits/Request/RawInput.php | 46 +++--- src/Http/Traits/Request/Route.php | 10 +- src/Http/Traits/Request/Url.php | 50 +++---- src/Http/Traits/Response/Body.php | 102 ++++++------- src/Http/Traits/Response/Header.php | 38 ++--- src/Http/Traits/Response/Status.php | 24 +-- src/Lang/Factories/LangFactory.php | 5 +- src/Router/Helpers/router.php | 40 ++--- src/Tracer/ErrorHandler.php | 8 +- tests/Unit/App/Adapters/WebAppAdapterTest.php | 10 +- tests/Unit/App/AppTest.php | 4 +- tests/Unit/AppTestCase.php | 15 +- tests/Unit/Csrf/CsrfTest.php | 2 +- tests/Unit/Http/Helpers/HttpHelperTest.php | 26 ++-- tests/Unit/Http/RequestTest.php | 9 +- tests/Unit/Http/ResponseTest.php | 9 +- .../Http/Traits/Request/HttpRawInputTest.php | 13 +- .../Traits/Request/HttpRequestBodyTest.php | 7 +- .../Traits/Request/HttpRequestFileTest.php | 7 +- .../Traits/Request/HttpRequestHeaderTest.php | 23 ++- .../Request/HttpRequestInternalTest.php | 20 ++- .../Traits/Request/HttpRequestQueryTest.php | 7 +- .../Traits/Request/HttpRequestRouteTest.php | 26 ++-- .../Traits/Request/HttpRequestUrlTest.php | 13 +- .../Traits/Response/HttpResponseBodyTest.php | 19 ++- .../Response/HttpResponseHeaderTest.php | 3 +- .../Response/HttpResponseStatusTest.php | 5 +- tests/Unit/Lang/LangTest.php | 10 +- tests/Unit/Module/ModuleManagerTest.php | 3 +- .../Renderer/Adapters/HtmlAdapterTest.php | 7 +- .../Renderer/Adapters/TwigAdapterTest.php | 8 +- tests/Unit/Renderer/RendererTest.php | 10 +- tests/Unit/ResourceCache/ViewCacheTest.php | 3 +- .../Unit/Router/Helpers/RouteHelpersTest.php | 18 +-- tests/Unit/Router/RouteFinderTest.php | 13 +- tests/Unit/Validation/Traits/FileRuleTest.php | 2 +- tests/Unit/View/Helpers/ViewHelperTest.php | 8 +- tests/Unit/View/QtViewTest.php | 7 +- 51 files changed, 548 insertions(+), 630 deletions(-) create mode 100644 src/App/Stages/InitHttpStage.php diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 49a4f772..45a680db 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -17,15 +17,13 @@ namespace Quantum\App\Adapters; use Quantum\Middleware\Exceptions\MiddlewareException; -use Quantum\Database\Exceptions\DatabaseException; use Quantum\App\Exceptions\StopExecutionException; -use Quantum\Session\Exceptions\SessionException; +use Quantum\Loader\Exceptions\LoaderException; use Quantum\Module\Exceptions\ModuleException; use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Stages\SetupErrorHandlerStage; use Quantum\Router\Exceptions\RouteException; use Quantum\App\Stages\LoadEnvironmentStage; -use Quantum\Http\Exceptions\HttpException; use Quantum\Csrf\Exceptions\CsrfException; use Quantum\Lang\Exceptions\LangException; use Quantum\App\Stages\LoadAppConfigStage; @@ -33,19 +31,17 @@ use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Exceptions\BaseException; use Quantum\App\Stages\LoadHelpersStage; +use Quantum\App\Stages\InitHttpStage; use Quantum\Di\Exceptions\DiException; use Quantum\App\Traits\WebAppTrait; use Quantum\Router\RouteCollection; use Quantum\Router\RouteDispatcher; use Quantum\Router\RouteBuilder; use Quantum\Module\ModuleLoader; -use DebugBar\DebugBarException; use Quantum\Router\RouteFinder; use Quantum\Debugger\Debugger; use Quantum\App\Enums\AppType; use Quantum\App\BootPipeline; -use Quantum\Http\Response; -use Quantum\Http\Request; use ReflectionException; use Quantum\Di\Di; @@ -57,21 +53,6 @@ class WebAppAdapter extends AppAdapter { use WebAppTrait; - /** - * @var Request - */ - private $request; - - /** - * @var Response - */ - private $response; - - /** - * @throws BaseException - * @throws DiException - * @throws ReflectionException - */ public function __construct() { parent::__construct(AppType::WEB); @@ -81,36 +62,20 @@ public function __construct() new LoadEnvironmentStage(), new LoadAppConfigStage(), new SetupErrorHandlerStage(), + new InitHttpStage(), ]); $pipeline->run($this->context); - - $this->request = Di::get(Request::class); - $this->response = Di::get(Response::class); } /** * Starts the web app - * @throws BaseException - * @throws ConfigException - * @throws CsrfException - * @throws DatabaseException - * @throws DebugBarException - * @throws DiException - * @throws HttpException - * @throws LangException - * @throws ModuleException - * @throws ReflectionException - * @throws RouteException - * @throws SessionException - * @throws MiddlewareException + * @throws ModuleException|MiddlewareException|LangException|RouteException|CsrfException|ConfigException|DiException|BaseException|LoaderException|ReflectionException */ public function start(): ?int { try { - $this->initializeRequestResponse($this->request, $this->response); - - if ($this->request->isMethod('OPTIONS')) { + if (request()->isMethod('OPTIONS')) { stop(); } @@ -129,14 +94,14 @@ public function start(): ?int $routeFinder = new RouteFinder($collection); - $matchedRoute = $routeFinder->find($this->request); + $matchedRoute = $routeFinder->find(request()); if ($matchedRoute === null) { page_not_found(); stop(); } - $this->request->setMatchedRoute($matchedRoute); + request()->setMatchedRoute($matchedRoute); (new LoadLanguageStage())->process($this->context); @@ -147,23 +112,20 @@ public function start(): ?int $middlewareManager = new MiddlewareManager($matchedRoute); - [$this->request, $this->response] = $middlewareManager->applyMiddlewares( - $this->request, - $this->response - ); + [$request, $response] = $middlewareManager->applyMiddlewares(request(), response()); $viewCache = $this->setupViewCache(); - if ($viewCache->serveCachedView(route_uri() ?? '', $this->response)) { + if ($viewCache->serveCachedView(route_uri() ?? '', $response)) { stop(); } $dispatcher = new RouteDispatcher(); - $dispatcher->dispatch($matchedRoute, $this->request); + $dispatcher->dispatch($matchedRoute, $request); stop(); } catch (StopExecutionException $exception) { - $this->handleCors($this->response); - $this->response->send(); + $this->handleCors(response()); + response()->send(); return $exception->getCode(); } diff --git a/src/App/Stages/InitHttpStage.php b/src/App/Stages/InitHttpStage.php new file mode 100644 index 00000000..a020e809 --- /dev/null +++ b/src/App/Stages/InitHttpStage.php @@ -0,0 +1,41 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Di\Exceptions\DiException; +use Quantum\App\AppContext; +use Quantum\Http\Response; +use Quantum\Http\Request; +use ReflectionException; +use Quantum\Di\Di; + +/** + * Class InitHttpStage + * @package Quantum\App + */ +class InitHttpStage implements BootStageInterface +{ + /** + * @throws DiException|ReflectionException + */ + public function process(AppContext $context): void + { + Di::get(Request::class); + Di::get(Response::class); + } +} diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index cd296f7f..bab03e82 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -18,13 +18,10 @@ use Quantum\Config\Exceptions\ConfigException; use Quantum\Loader\Exceptions\LoaderException; -use Quantum\Http\Exceptions\HttpException; -use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; use Quantum\Debugger\Debugger; use Quantum\Http\Response; -use Quantum\Http\Request; use Quantum\Loader\Setup; use ReflectionException; use Quantum\Di\Di; @@ -35,15 +32,6 @@ */ trait WebAppTrait { - /** - * @throws BaseException|HttpException|DiException|ReflectionException - */ - private function initializeRequestResponse(Request $request, Response $response): void - { - $request->init(server()); - $response->init(); - } - /** * @throws DiException|ReflectionException */ diff --git a/src/Auth/Adapters/JwtAuthAdapter.php b/src/Auth/Adapters/JwtAuthAdapter.php index db91f780..13298214 100644 --- a/src/Auth/Adapters/JwtAuthAdapter.php +++ b/src/Auth/Adapters/JwtAuthAdapter.php @@ -20,13 +20,13 @@ use Quantum\Auth\Contracts\AuthServiceInterface; use Quantum\Auth\Exceptions\AuthException; use Quantum\Jwt\Exceptions\JwtException; +use Quantum\Di\Exceptions\DiException; use Quantum\Auth\Traits\AuthTrait; use Quantum\Auth\Enums\AuthKeys; use Quantum\Mailer\Mailer; use Quantum\Hasher\Hasher; -use Quantum\Http\Response; -use Quantum\Http\Request; use Quantum\Jwt\JwtToken; +use ReflectionException; use Quantum\Auth\User; use Exception; @@ -57,9 +57,7 @@ public function __construct(AuthServiceInterface $authService, Mailer $mailer, H /** * @inheritDoc - * @throws AuthException - * @throws JwtException - * @throws Exception + * @throws AuthException|DiException|JwtException|ReflectionException|Exception */ public function signin(string $username, string $password) { @@ -74,10 +72,11 @@ public function signin(string $username, string $password) /** * @inheritDoc + * @throws DiException|ReflectionException */ public function signout(): bool { - $refreshToken = Request::getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]); + $refreshToken = request()->getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]); $user = $this->authService->get($this->keyFields[AuthKeys::REFRESH_TOKEN], $refreshToken); @@ -88,9 +87,9 @@ public function signout(): bool array_merge($this->getVisibleFields($user), [$this->keyFields[AuthKeys::REFRESH_TOKEN] => '']) ); - Request::deleteHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]); - Request::deleteHeader('Authorization'); - Response::delete('tokens'); + request()->deleteHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]); + request()->deleteHeader('Authorization'); + response()->delete('tokens'); return true; } @@ -100,7 +99,7 @@ public function signout(): bool /** * @inheritDoc - * @throws JwtException + * @throws JwtException|DiException|ReflectionException */ public function user(): ?User { @@ -113,7 +112,7 @@ public function user(): ?User /** * Refresh user data - * @throws JwtException + * @throws JwtException|DiException|ReflectionException */ public function refreshUser(string $uuid): bool { @@ -130,9 +129,8 @@ public function refreshUser(string $uuid): bool /** * Verify OTP - * @throws AuthException - * @throws JwtException * @return array + * @throws AuthException|JwtException|DiException|ReflectionException */ public function verifyOtp(int $otp, string $otpToken): array { @@ -157,7 +155,7 @@ protected function getUpdatedTokens(User $user): array /** * Set Updated Tokens * @return array - * @throws JwtException + * @throws JwtException|DiException|ReflectionException */ protected function setUpdatedTokens(User $user): array { @@ -169,9 +167,9 @@ protected function setUpdatedTokens(User $user): array array_merge($this->getVisibleFields($user), [$this->keyFields[AuthKeys::REFRESH_TOKEN] => $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN]]]) ); - Request::setHeader($this->keyFields[AuthKeys::REFRESH_TOKEN], $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN]]); - Request::setHeader('Authorization', 'Bearer ' . $tokens[$this->keyFields[AuthKeys::ACCESS_TOKEN]]); - Response::set('tokens', $tokens); + request()->setHeader($this->keyFields[AuthKeys::REFRESH_TOKEN], $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN]]); + request()->setHeader('Authorization', 'Bearer ' . $tokens[$this->keyFields[AuthKeys::ACCESS_TOKEN]]); + response()->set('tokens', $tokens); return $tokens; } @@ -183,13 +181,16 @@ protected function checkRefreshToken(): ?User { return $this->authService->get( $this->keyFields[AuthKeys::REFRESH_TOKEN], - Request::getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]) + request()->getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]) ); } + /** + * @throws DiException|ReflectionException + */ private function getUserFromAccessToken(): ?User { - $authorizationBearer = Request::getAuthorizationBearer(); + $authorizationBearer = request()->getAuthorizationBearer(); if (!$authorizationBearer) { return null; @@ -203,11 +204,11 @@ private function getUserFromAccessToken(): ?User } /** - * @throws JwtException + * @throws JwtException|DiException|ReflectionException */ private function getUserFromRefreshToken(): ?User { - if (!Request::hasHeader($this->keyFields[AuthKeys::REFRESH_TOKEN])) { + if (!request()->hasHeader($this->keyFields[AuthKeys::REFRESH_TOKEN])) { return null; } diff --git a/src/Http/Helpers/http.php b/src/Http/Helpers/http.php index 9b680e99..799aab6a 100644 --- a/src/Http/Helpers/http.php +++ b/src/Http/Helpers/http.php @@ -12,6 +12,7 @@ * @since 3.0.0 */ +use Quantum\App\Exceptions\StopExecutionException; use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; @@ -20,6 +21,25 @@ use Quantum\Http\Enums\StatusCode; use Quantum\Http\Response; use Quantum\Http\Request; +use Quantum\Di\Di; + +/** + * Gets the Request instance from DI + * @throws DiException|ReflectionException + */ +function request(): Request +{ + return Di::get(Request::class); +} + +/** + * Gets the Response instance from DI + * @throws DiException|ReflectionException + */ +function response(): Response +{ + return Di::get(Response::class); +} /** * Gets the base url @@ -28,23 +48,25 @@ */ function base_url(bool $withModulePrefix = false): string { - return Request::getBaseUrl($withModulePrefix); + return request()->getBaseUrl($withModulePrefix); } /** * Gets the current url + * @throws DiException|ReflectionException */ function current_url(): string { - return Request::getCurrentUrl(); + return request()->getCurrentUrl(); } /** * Redirect + * @throws StopExecutionException|DiException|ReflectionException */ function redirect(string $url, int $code = StatusCode::FOUND): void { - Response::redirect($url, $code); + response()->redirect($url, $code); } /** @@ -55,7 +77,7 @@ function redirect(string $url, int $code = StatusCode::FOUND): void function redirectWith(string $url, array $data, int $code = StatusCode::FOUND): void { session()->set(ReservedKeys::PREV_REQUEST, $data); - Response::redirect($url, $code); + response()->redirect($url, $code); } /** @@ -85,10 +107,11 @@ function old(string $key) /** * Gets the referrer + * @throws DiException|ReflectionException */ function get_referrer(): ?string { - return Request::getReferrer(); + return request()->getReferrer(); } /** @@ -97,17 +120,17 @@ function get_referrer(): ?string */ function page_not_found(): void { - $acceptHeader = Response::getHeader('Accept'); + $acceptHeader = response()->getHeader('Accept'); $isJson = $acceptHeader === ContentType::JSON; if ($isJson) { - Response::json( + response()->json( ['status' => 'error', 'message' => 'Page not found',], StatusCode::NOT_FOUND ); } else { - Response::html( + response()->html( partial('errors' . DS . StatusCode::NOT_FOUND), StatusCode::NOT_FOUND ); diff --git a/src/Http/Request.php b/src/Http/Request.php index 8b4b19b7..daa4e6b4 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -67,66 +67,61 @@ class Request /** * Request method */ - private static ?string $__method = null; + private ?string $__method = null; - protected static Server $server; - - private static bool $initialized = false; + protected Server $server; /** - * Initializes the request properties using the server instance. * @throws ConfigException|DiException|BaseException|ReflectionException */ - public static function init(Server $server): void + public function __construct(?Server $server = null) { - if (self::$initialized) { - return; - } - - self::flush(); - - self::$server = $server; - - self::setServerInfo(); - self::setContentType(); - self::setRequestHeaders(); - - ['params' => $rawInputParams, 'files' => $rawInputFiles] = self::getRawInputParams(); - - self::setRequestParams($rawInputParams); - self::setUploadedFiles($rawInputFiles); - - self::$initialized = true; + $this->server = $server ?? server(); + $this->populateFromServer(); } /** * Flushes the request header, body and files */ - public static function flush(): void + public function flush(): void { - self::$__headers = []; - self::$__request = []; - self::$__files = []; - self::$__protocol = null; - self::$__host = null; - self::$__port = null; - self::$__uri = null; - self::$__query = null; - - self::$initialized = false; + $this->__headers = []; + $this->__request = []; + $this->__files = []; + $this->__protocol = null; + $this->__host = null; + $this->__port = null; + $this->__uri = null; + $this->__query = null; + } + + /** + * Re-reads method, headers, params and files from the current server state. + * @throws ConfigException|DiException|BaseException|ReflectionException + */ + protected function populateFromServer(): void + { + $this->setServerInfo(); + $this->setContentType(); + $this->setRequestHeaders(); + + ['params' => $rawInputParams, 'files' => $rawInputFiles] = $this->getRawInputParams(); + + $this->setRequestParams($rawInputParams); + $this->setUploadedFiles($rawInputFiles); } /** * Sets the merged request parameters * @param array $params */ - public static function setRequestParams(array $params): void + public function setRequestParams(array $params): void { - self::$__request = array_merge( - self::getParams(), - self::postParams(), - self::jsonPayloadParams(), - self::urlEncodedParams(), + $this->__request = array_merge( + $this->getParams(), + $this->postParams(), + $this->jsonPayloadParams(), + $this->urlEncodedParams(), $params ); } @@ -137,10 +132,10 @@ public static function setRequestParams(array $params): void * @throws BaseException * @throws ReflectionException */ - public static function setUploadedFiles(array $files): void + public function setUploadedFiles(array $files): void { - self::$__files = array_merge( - self::handleFiles($_FILES), + $this->__files = array_merge( + $this->handleFiles($_FILES), $files ); } @@ -148,43 +143,43 @@ public static function setUploadedFiles(array $files): void /** * Gets the request method */ - public static function getMethod(): ?string + public function getMethod(): ?string { - return self::$__method; + return $this->__method; } /** * Sets the request method * @throws BaseException */ - public static function setMethod(string $method): void + public function setMethod(string $method): void { if (!in_array(strtoupper($method), self::METHODS)) { throw HttpException::requestMethodNotAvailable($method); } - self::$__method = $method; + $this->__method = $method; } /** * Checks if the current method matches the given method */ - public static function isMethod(string $method): bool + public function isMethod(string $method): bool { - return strcasecmp($method, self::$__method ?? '') === 0; + return strcasecmp($method, $this->__method ?? '') === 0; } /** * Gets Cross Site Request Forgery Token */ - public static function getCsrfToken(): ?string + public function getCsrfToken(): ?string { $csrfToken = null; - if (self::has(Csrf::TOKEN_KEY)) { - $csrfToken = (string) self::get(Csrf::TOKEN_KEY); - } elseif (self::hasHeader('X-' . Csrf::TOKEN_KEY)) { - $csrfToken = self::getHeader('X-' . Csrf::TOKEN_KEY); + if ($this->has(Csrf::TOKEN_KEY)) { + $csrfToken = (string) $this->get(Csrf::TOKEN_KEY); + } elseif ($this->hasHeader('X-' . Csrf::TOKEN_KEY)) { + $csrfToken = $this->getHeader('X-' . Csrf::TOKEN_KEY); } return $csrfToken; @@ -194,7 +189,7 @@ public static function getCsrfToken(): ?string * Gets the base url * @throws DiException|ReflectionException */ - public static function getBaseUrl(bool $withModulePrefix = false): string + public function getBaseUrl(bool $withModulePrefix = false): string { $baseUrl = config()->get('app.base_url'); @@ -205,29 +200,29 @@ public static function getBaseUrl(bool $withModulePrefix = false): string return $baseUrl . $modulePrefix; } - return self::getHostPrefix() . $modulePrefix; + return $this->getHostPrefix() . $modulePrefix; } /** * Gets the current url */ - public static function getCurrentUrl(): string + public function getCurrentUrl(): string { - $uri = self::getUri(); - $query = self::getQuery(); + $uri = $this->getUri(); + $query = $this->getQuery(); $queryPart = $query ? '?' . $query : ''; - return self::getHostPrefix() . '/' . $uri . $queryPart; + return $this->getHostPrefix() . '/' . $uri . $queryPart; } /** * Gets the protocol, host, and optional port part of the URL. */ - private static function getHostPrefix(): string + private function getHostPrefix(): string { - $protocol = self::getProtocol(); - $host = self::getHost(); - $port = self::getPort(); + $protocol = $this->getProtocol(); + $host = $this->getHost(); + $port = $this->getPort(); $defaultPort = $protocol === 'https' ? self::DEFAULT_HTTPS_PORT : self::DEFAULT_HTTP_PORT; @@ -239,27 +234,27 @@ private static function getHostPrefix(): string /** * Sets server data (method, protocol, host, port, uri, query). */ - private static function setServerInfo(): void + private function setServerInfo(): void { foreach (['method', 'protocol', 'host', 'port', 'uri', 'query'] as $name) { - self::${"__{$name}"} = self::$server->$name(); + $this->{"__{$name}"} = $this->server->$name(); } } /** * Sets the normalized request content type. */ - private static function setContentType(): void + private function setContentType(): void { - self::$__contentType = self::$server->contentType(true); + $this->__contentType = $this->server->contentType(true); } /** * Sets request headers, normalizing keys to lowercase. * @throws DiException|ReflectionException */ - private static function setRequestHeaders(): void + private function setRequestHeaders(): void { - self::$__headers = array_change_key_case(getallheaders()); + $this->__headers = array_change_key_case(getallheaders()); } } diff --git a/src/Http/Response.php b/src/Http/Response.php index 25dbed7d..11c9beef 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -38,48 +38,31 @@ class Response * XML root element * @var string */ - private static string $xmlRoot = ''; + private string $xmlRoot = ''; /** * Callback function * @var string */ - private static string $callbackFunction = ''; - - private static bool $initialized = false; - - /** - * Initialize the Response - */ - public static function init(): void - { - if (self::$initialized) { - return; - } - - self::flush(); - - self::$initialized = true; - } + private string $callbackFunction = ''; /** * Flushes the response header and body */ - public static function flush(): void + public function flush(): void { - self::$__statusCode = StatusCode::OK; - self::$__headers = []; - self::$__response = []; - self::$xmlRoot = ''; - self::$callbackFunction = ''; - self::$initialized = false; + $this->__statusCode = StatusCode::OK; + $this->__headers = []; + $this->__response = []; + $this->xmlRoot = ''; + $this->callbackFunction = ''; } /** * Sends all response data to the client and finishes the request. * @throws Exception */ - public static function send(): void + public function send(): void { if (Environment::getInstance()->getAppEnv() !== Env::TESTING) { while (ob_get_level() > 0) { @@ -87,12 +70,12 @@ public static function send(): void } } - foreach (self::$__headers as $key => $value) { + foreach ($this->__headers as $key => $value) { header($key . ': ' . $value); } - http_response_code(self::getStatusCode()); + http_response_code($this->getStatusCode()); - echo self::getContent(); + echo $this->getContent(); } } diff --git a/src/Http/Traits/Request/Body.php b/src/Http/Traits/Request/Body.php index 62ae2453..3678c29c 100644 --- a/src/Http/Traits/Request/Body.php +++ b/src/Http/Traits/Request/Body.php @@ -29,27 +29,27 @@ trait Body * Request body * @var array */ - private static array $__request = []; + private array $__request = []; /** * Checks if request contains a data by given key */ - public static function has(string $key): bool + public function has(string $key): bool { - return isset(self::$__request[$key]); + return isset($this->__request[$key]); } /** * Retrieves data from request by given key * @return mixed */ - public static function get(string $key, ?string $default = null, bool $raw = false) + public function get(string $key, ?string $default = null, bool $raw = false) { - if (!self::has($key)) { + if (!$this->has($key)) { return $default; } - $value = self::$__request[$key]; + $value = $this->__request[$key]; if ($raw) { return $value; @@ -64,31 +64,31 @@ public static function get(string $key, ?string $default = null, bool $raw = fal * Sets new key/value pair into request * @param mixed $value */ - public static function set(string $key, $value): void + public function set(string $key, $value): void { if ($key === ReservedKeys::RENDERED_VIEW) { throw new InvalidArgumentException("Cannot set reserved key: `$key`"); } - self::$__request[$key] = $value; + $this->__request[$key] = $value; } /** * Gets all request parameters * @return array */ - public static function all(): array + public function all(): array { - return array_merge(self::$__request, self::$__files); + return array_merge($this->__request, $this->__files); } /** * Deletes the element from request by given key */ - public static function delete(string $key): void + public function delete(string $key): void { - if (self::has($key)) { - unset(self::$__request[$key]); + if ($this->has($key)) { + unset($this->__request[$key]); } } } diff --git a/src/Http/Traits/Request/File.php b/src/Http/Traits/Request/File.php index f91727ee..703efd46 100644 --- a/src/Http/Traits/Request/File.php +++ b/src/Http/Traits/Request/File.php @@ -31,23 +31,23 @@ trait File * Files * @var array */ - private static array $__files = []; + private array $__files = []; /** * Checks to see if request contains file */ - public static function hasFile(string $key): bool + public function hasFile(string $key): bool { - if (!isset(self::$__files[$key])) { + if (!isset($this->__files[$key])) { return false; } - if (!is_array(self::$__files[$key]) && self::$__files[$key]->getErrorCode() != UPLOAD_ERR_OK) { + if (!is_array($this->__files[$key]) && $this->__files[$key]->getErrorCode() != UPLOAD_ERR_OK) { return false; } - if (is_array(self::$__files[$key])) { - foreach (self::$__files[$key] as $file) { + if (is_array($this->__files[$key])) { + foreach ($this->__files[$key] as $file) { if ($file->getErrorCode() != UPLOAD_ERR_OK) { return false; } @@ -63,13 +63,13 @@ public static function hasFile(string $key): bool * @return mixed * @throws BaseException */ - public static function getFile(string $key) + public function getFile(string $key) { - if (!self::hasFile($key)) { + if (!$this->hasFile($key)) { throw FileUploadException::fileNotFound($key); } - return self::$__files[$key]; + return $this->__files[$key]; } /** @@ -79,7 +79,7 @@ public static function getFile(string $key) * @throws BaseException * @throws ReflectionException */ - public static function handleFiles(array $files): array + public function handleFiles(array $files): array { if (!count($files)) { return []; diff --git a/src/Http/Traits/Request/Header.php b/src/Http/Traits/Request/Header.php index fbf86f95..aa0b12b3 100644 --- a/src/Http/Traits/Request/Header.php +++ b/src/Http/Traits/Request/Header.php @@ -26,26 +26,26 @@ trait Header * Request headers * @var array */ - private static array $__headers = []; + private array $__headers = []; /** * Checks the request header existence by given key */ - public static function hasHeader(string $key): bool + public function hasHeader(string $key): bool { - [$keyWithHyphens, $keyWithUnderscores] = self::normalizeHeaderKey($key); + [$keyWithHyphens, $keyWithUnderscores] = $this->normalizeHeaderKey($key); - return isset(self::$__headers[$keyWithHyphens]) || isset(self::$__headers[$keyWithUnderscores]); + return isset($this->__headers[$keyWithHyphens]) || isset($this->__headers[$keyWithUnderscores]); } /** * Gets the request header by given key */ - public static function getHeader(string $key): ?string + public function getHeader(string $key): ?string { - if (self::hasHeader($key)) { - [$keyWithHyphens, $keyWithUnderscores] = self::normalizeHeaderKey($key); - return self::$__headers[$keyWithHyphens] ?? self::$__headers[$keyWithUnderscores]; + if ($this->hasHeader($key)) { + [$keyWithHyphens, $keyWithUnderscores] = $this->normalizeHeaderKey($key); + return $this->__headers[$keyWithHyphens] ?? $this->__headers[$keyWithUnderscores]; } return null; @@ -55,40 +55,40 @@ public static function getHeader(string $key): ?string * Sets the request header * @param mixed $value */ - public static function setHeader(string $key, $value): void + public function setHeader(string $key, $value): void { - self::$__headers[strtolower($key)] = $value; + $this->__headers[strtolower($key)] = $value; } /** * Gets all request headers * @return array */ - public static function allHeaders(): array + public function allHeaders(): array { - return self::$__headers; + return $this->__headers; } /** * Deletes the header by given key */ - public static function deleteHeader(string $key): void + public function deleteHeader(string $key): void { - if (self::hasHeader($key)) { - unset(self::$__headers[strtolower($key)]); + if ($this->hasHeader($key)) { + unset($this->__headers[strtolower($key)]); } } /** * Gets Authorization Bearer token */ - public static function getAuthorizationBearer(): ?string + public function getAuthorizationBearer(): ?string { $bearerToken = null; - $authorization = (string) self::getHeader('Authorization'); + $authorization = (string) $this->getHeader('Authorization'); - if (self::hasHeader('Authorization') && preg_match('/Bearer\s(\S+)/', $authorization, $matches)) { + if ($this->hasHeader('Authorization') && preg_match('/Bearer\s(\S+)/', $authorization, $matches)) { $bearerToken = $matches[1]; } @@ -99,20 +99,20 @@ public static function getAuthorizationBearer(): ?string * Gets Basic Auth Credentials * @return array|null */ - public static function getBasicAuthCredentials(): ?array + public function getBasicAuthCredentials(): ?array { - if (self::$server->has('PHP_AUTH_USER') && static::$server->has('PHP_AUTH_PW')) { + if ($this->server->has('PHP_AUTH_USER') && $this->server->has('PHP_AUTH_PW')) { return [ - 'username' => self::$server->get('PHP_AUTH_USER'), - 'password' => self::$server->get('PHP_AUTH_PW'), + 'username' => $this->server->get('PHP_AUTH_USER'), + 'password' => $this->server->get('PHP_AUTH_PW'), ]; } - if (!self::hasHeader('Authorization')) { + if (!$this->hasHeader('Authorization')) { return null; } - $authorization = (string) self::getHeader('Authorization'); + $authorization = (string) $this->getHeader('Authorization'); if (preg_match('/Basic\s(\S+)/', $authorization, $matches)) { $decoded = base64_decode($matches[1], true); @@ -129,23 +129,23 @@ public static function getBasicAuthCredentials(): ?array /** * Checks to see if request was AJAX request */ - public static function isAjax(): bool + public function isAjax(): bool { - return self::hasHeader('X-REQUESTED-WITH') || self::$server->ajax(); + return $this->hasHeader('X-REQUESTED-WITH') || $this->server->ajax(); } /** * Gets the referrer */ - public static function getReferrer(): ?string + public function getReferrer(): ?string { - return self::$server->referrer(); + return $this->server->referrer(); } /** * @return array */ - private static function normalizeHeaderKey(string $key): array + private function normalizeHeaderKey(string $key): array { $keyWithHyphens = str_replace('_', '-', strtolower($key)); $keyWithUnderscores = str_replace('-', '_', $key); diff --git a/src/Http/Traits/Request/Internal.php b/src/Http/Traits/Request/Internal.php index da11a08c..156c1e2f 100644 --- a/src/Http/Traits/Request/Internal.php +++ b/src/Http/Traits/Request/Internal.php @@ -38,7 +38,7 @@ trait Internal * @param array $files * @throws ConfigException|DiException|BaseException|ReflectionException */ - public static function create( + public function create( string $method, string $url, array $params = [], @@ -79,22 +79,23 @@ public static function create( $server->set('QUERY_STRING', ''); } - self::detectAndSetContentType($server, $params, $files); + $this->detectAndSetContentType($server, $params, $files); foreach ($headers as $name => $value) { $server->set('HTTP_' . strtoupper(str_replace('-', '_', $name)), $value); } - self::flush(); + $this->flush(); - self::init($server); + $this->server = $server; + $this->populateFromServer(); if ($params !== []) { - self::setRequestParams($params); + $this->setRequestParams($params); } if ($files !== []) { - self::setUploadedFiles(self::handleFiles($files)); + $this->setUploadedFiles($this->handleFiles($files)); } } @@ -103,7 +104,7 @@ public static function create( * @param array|null $data * @param array|null $files */ - protected static function detectAndSetContentType(Server $server, ?array $data = null, ?array $files = null): void + protected function detectAndSetContentType(Server $server, ?array $data = null, ?array $files = null): void { if ($files && count($files) > 0) { $server->set('CONTENT_TYPE', ContentType::FORM_DATA); diff --git a/src/Http/Traits/Request/Params.php b/src/Http/Traits/Request/Params.php index a0250d4f..e1a8be60 100644 --- a/src/Http/Traits/Request/Params.php +++ b/src/Http/Traits/Request/Params.php @@ -32,13 +32,13 @@ trait Params * Request content type * @var string|null */ - private static ?string $__contentType; + private ?string $__contentType = null; /** * Gets the GET params. * @return array */ - private static function getParams(): array + private function getParams(): array { if ($_GET === []) { return []; @@ -51,7 +51,7 @@ private static function getParams(): array * Gets the POST params. * @return array */ - private static function postParams(): array + private function postParams(): array { if ($_POST === []) { return []; @@ -64,32 +64,32 @@ private static function postParams(): array * Parses and returns JSON payload parameters. * @return array */ - private static function jsonPayloadParams(): array + private function jsonPayloadParams(): array { if ( - !in_array(self::$__method, ['PUT', 'PATCH', 'POST'], true) || - self::$__contentType !== ContentType::JSON + !in_array($this->__method, ['PUT', 'PATCH', 'POST'], true) || + $this->__contentType !== ContentType::JSON ) { return []; } - return json_decode(self::getRawInput(), true) ?: []; + return json_decode($this->getRawInput(), true) ?: []; } /** * Parses and returns URL-encoded parameters. * @return array */ - private static function urlEncodedParams(): array + private function urlEncodedParams(): array { if ( - !in_array(self::$__method, ['PUT', 'PATCH', 'POST'], true) || - self::$__contentType !== ContentType::URL_ENCODED + !in_array($this->__method, ['PUT', 'PATCH', 'POST'], true) || + $this->__contentType !== ContentType::URL_ENCODED ) { return []; } - parse_str(urldecode(self::getRawInput()), $result); + parse_str(urldecode($this->getRawInput()), $result); return $result; /** @phpstan-ignore return.type */ } @@ -99,22 +99,22 @@ private static function urlEncodedParams(): array * @return array * @throws ConfigException|DiException|BaseException|ReflectionException */ - private static function getRawInputParams(): array + private function getRawInputParams(): array { if ( - !in_array(self::$__method, ['PUT', 'PATCH', 'POST'], true) || - self::$__contentType !== ContentType::FORM_DATA + !in_array($this->__method, ['PUT', 'PATCH', 'POST'], true) || + $this->__contentType !== ContentType::FORM_DATA ) { return ['params' => [], 'files' => []]; } - return self::parse(self::getRawInput()); + return $this->parse($this->getRawInput()); } /** * Retrieves the raw HTTP request body as a string. */ - private static function getRawInput(): string + private function getRawInput(): string { return file_get_contents('php://input') ?: ''; } diff --git a/src/Http/Traits/Request/Query.php b/src/Http/Traits/Request/Query.php index feef85c3..f3408ad2 100644 --- a/src/Http/Traits/Request/Query.php +++ b/src/Http/Traits/Request/Query.php @@ -25,34 +25,34 @@ trait Query /** * Query string */ - private static ?string $__query = null; + private ?string $__query = null; /** * Gets the query string */ - public static function getQuery(): ?string + public function getQuery(): ?string { - return self::$__query; + return $this->__query; } /** * Sets the query string */ - public static function setQuery(string $query): void + public function setQuery(string $query): void { - self::$__query = $query; + $this->__query = $query; } /** * Gets the query param */ - public static function getQueryParam(string $key): ?string + public function getQueryParam(string $key): ?string { - if (self::$__query === null) { + if ($this->__query === null) { return null; } - $query = explode('&', self::$__query); + $query = explode('&', $this->__query); foreach ($query as $items) { $item = explode('=', $items); @@ -67,10 +67,10 @@ public static function getQueryParam(string $key): ?string /** * Sets the query param */ - public static function setQueryParam(string $key, string $value): void + public function setQueryParam(string $key, string $value): void { - $queryParams = self::$__query ? explode('&', self::$__query) : []; + $queryParams = $this->__query ? explode('&', $this->__query) : []; $queryParams[] = $key . '=' . $value; - self::$__query = implode('&', $queryParams); + $this->__query = implode('&', $queryParams); } } diff --git a/src/Http/Traits/Request/RawInput.php b/src/Http/Traits/Request/RawInput.php index ad062ace..9c5707c3 100644 --- a/src/Http/Traits/Request/RawInput.php +++ b/src/Http/Traits/Request/RawInput.php @@ -35,23 +35,23 @@ trait RawInput * @return array * @throws ConfigException|DiException|BaseException|ReflectionException */ - public static function parse(string $rawInput): array + public function parse(string $rawInput): array { - $boundary = self::getBoundary(); + $boundary = $this->getBoundary(); if (!$boundary) { return ['params' => [], 'files' => []]; } - $blocks = self::getBlocks($boundary, $rawInput); + $blocks = $this->getBlocks($boundary, $rawInput); - return self::processBlocks($blocks); + return $this->processBlocks($blocks); } /** * Extracts boundary string from Content-Type header */ - private static function getBoundary(): ?string + private function getBoundary(): ?string { $contentType = server()->contentType(); @@ -68,7 +68,7 @@ private static function getBoundary(): ?string * Splits raw input into multipart blocks * @return array */ - private static function getBlocks(string $boundary, string $rawInput): array + private function getBlocks(string $boundary, string $rawInput): array { $result = preg_split("/-+$boundary/", $rawInput); @@ -87,7 +87,7 @@ private static function getBlocks(string $boundary, string $rawInput): array * @return array * @throws ConfigException|DiException|BaseException|ReflectionException */ - private static function processBlocks(array $blocks): array + private function processBlocks(array $blocks): array { $params = []; $files = []; @@ -99,11 +99,11 @@ private static function processBlocks(array $blocks): array continue; } - $type = self::detectBlockType($block); + $type = $this->detectBlockType($block); switch ($type) { case 'file': - $parsed = self::getParsedFile($block); + $parsed = $this->getParsedFile($block); if ($parsed === null) { continue 2; @@ -111,16 +111,16 @@ private static function processBlocks(array $blocks): array [$nameParam, $file] = $parsed; - self::addFileToCollection($files, $nameParam, $file); + $this->addFileToCollection($files, $nameParam, $file); break; case 'stream': - $params += self::getParsedStream($block); + $params += $this->getParsedStream($block); break; case 'param': default: - $params += self::getParsedParameter($block); + $params += $this->getParsedParameter($block); break; } } @@ -132,9 +132,9 @@ private static function processBlocks(array $blocks): array * Adds a parsed file to the files collection * @param array $files */ - private static function addFileToCollection(array &$files, string $nameParam, UploadedFile $file): void + private function addFileToCollection(array &$files, string $nameParam, UploadedFile $file): void { - $arrayParam = self::arrayParam($nameParam); + $arrayParam = $this->arrayParam($nameParam); if (is_array($arrayParam)) { [$name, $key] = $arrayParam; @@ -153,7 +153,7 @@ private static function addFileToCollection(array &$files, string $nameParam, Up * Detects the block type as a string identifier. * @return string One of 'file', 'stream', 'param' */ - private static function detectBlockType(string $block): string + private function detectBlockType(string $block): string { if (strpos($block, 'filename') !== false) { return 'file'; @@ -170,7 +170,7 @@ private static function detectBlockType(string $block): string * Gets the parsed param * @return array */ - private static function getParsedStream(string $block): array + private function getParsedStream(string $block): array { preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $block, $match); @@ -182,9 +182,9 @@ private static function getParsedStream(string $block): array * @return array{string, UploadedFile}|null * @throws ConfigException|DiException|BaseException|ReflectionException */ - private static function getParsedFile(string $block): ?array + private function getParsedFile(string $block): ?array { - [$name, $filename, $type, $content] = self::parseFileData($block); + [$name, $filename, $type, $content] = $this->parseFileData($block); if (!$content) { return null; @@ -213,7 +213,7 @@ private static function getParsedFile(string $block): ?array * Parses a file block into metadata and binary content * @return array{string, string, string, string} */ - private static function parseFileData(string $block): array + private function parseFileData(string $block): array { $block = ltrim($block, "\r\n"); @@ -225,7 +225,7 @@ private static function parseFileData(string $block): array [$rawHeaders, $content] = $parts; - [$name, $filename, $contentType] = self::parseHeaders($rawHeaders); + [$name, $filename, $contentType] = $this->parseHeaders($rawHeaders); $content = substr($content, 0, strlen($content) - 2); @@ -241,7 +241,7 @@ private static function parseFileData(string $block): array * Parses a block and extracts normal form parameters * @return array */ - private static function getParsedParameter(string $block): array + private function getParsedParameter(string $block): array { $data = []; @@ -262,7 +262,7 @@ private static function getParsedParameter(string $block): array * Extracts name, filename, and content type from header lines * @return array{string, string, string} */ - private static function parseHeaders(string $rawHeaders): array + private function parseHeaders(string $rawHeaders): array { $name = '-unknown-'; $filename = '-unknown-'; @@ -294,7 +294,7 @@ private static function parseHeaders(string $rawHeaders): array * Parses array-like parameter names * @return array|string */ - private static function arrayParam(string $parameter) + private function arrayParam(string $parameter) { if (strpos($parameter, '[') !== false && preg_match('/^([^[]*)\[([^]]*)\](.*)$/', $parameter, $match)) { return [$match[1], $match[2]]; diff --git a/src/Http/Traits/Request/Route.php b/src/Http/Traits/Request/Route.php index b941e0b8..413b83ff 100644 --- a/src/Http/Traits/Request/Route.php +++ b/src/Http/Traits/Request/Route.php @@ -24,16 +24,16 @@ */ trait Route { - private static ?MatchedRoute $route = null; + private ?MatchedRoute $route = null; - public static function setMatchedRoute(?MatchedRoute $route): void + public function setMatchedRoute(?MatchedRoute $route): void { - self::$route = $route; + $this->route = $route; } - public static function getMatchedRoute(): ?MatchedRoute + public function getMatchedRoute(): ?MatchedRoute { - return self::$route; + return $this->route; } } diff --git a/src/Http/Traits/Request/Url.php b/src/Http/Traits/Request/Url.php index 87876039..f91ac037 100644 --- a/src/Http/Traits/Request/Url.php +++ b/src/Http/Traits/Request/Url.php @@ -25,96 +25,96 @@ trait Url /** * Scheme */ - private static ?string $__protocol = null; + private ?string $__protocol = null; /** * Host name */ - private static ?string $__host = null; + private ?string $__host = null; /** * Server port */ - private static ?string $__port = null; + private ?string $__port = null; /** * Request URI */ - private static ?string $__uri = null; + private ?string $__uri = null; /** * Gets the protocol * @return string */ - public static function getProtocol(): ?string + public function getProtocol(): ?string { - return self::$__protocol; + return $this->__protocol; } /** * Sets the protocol */ - public static function setProtocol(string $protocol): void + public function setProtocol(string $protocol): void { - self::$__protocol = $protocol; + $this->__protocol = $protocol; } /** * Gets the host name * @return string */ - public static function getHost(): ?string + public function getHost(): ?string { - return self::$__host; + return $this->__host; } /** * Sets the host name */ - public static function setHost(string $host): void + public function setHost(string $host): void { - self::$__host = $host; + $this->__host = $host; } /** * Gets the port * @return string */ - public static function getPort(): ?string + public function getPort(): ?string { - return self::$__port; + return $this->__port; } /** * Sets the port */ - public static function setPort(string $port): void + public function setPort(string $port): void { - self::$__port = $port; + $this->__port = $port; } /** * Gets the URI */ - public static function getUri(): ?string + public function getUri(): ?string { - return self::$__uri; + return $this->__uri; } /** * Sets the URI */ - public static function setUri(string $uri): void + public function setUri(string $uri): void { - self::$__uri = ltrim($uri, '/'); + $this->__uri = ltrim($uri, '/'); } /** * Returns the URI segment at the specified index. */ - public static function getSegment(int $index): ?string + public function getSegment(int $index): ?string { - $segments = self::getAllSegments(); + $segments = $this->getAllSegments(); return $segments[$index] ?? null; } @@ -123,13 +123,13 @@ public static function getSegment(int $index): ?string * Gets all URI segments as an array. * @return array */ - public static function getAllSegments(): array + public function getAllSegments(): array { - if (self::$__uri === null) { + if ($this->__uri === null) { return ['zero_segment']; } - $parsed = parse_url(self::$__uri); + $parsed = parse_url($this->__uri); $segments = explode('/', trim(is_array($parsed) ? ($parsed['path'] ?? '') : '', '/')); array_unshift($segments, 'zero_segment'); return $segments; diff --git a/src/Http/Traits/Response/Body.php b/src/Http/Traits/Response/Body.php index a1a0fc3d..3d21fcba 100644 --- a/src/Http/Traits/Response/Body.php +++ b/src/Http/Traits/Response/Body.php @@ -33,12 +33,12 @@ trait Body * Response * @var array */ - private static array $__response = []; + private array $__response = []; /** * @var string[] */ - private static array $formatters = [ + private array $formatters = [ ContentType::HTML => 'formatHtml', ContentType::XML => 'formatXml', ContentType::JSON => 'formatJson', @@ -48,45 +48,45 @@ trait Body /** * Checks if response contains a data by given key */ - public static function has(string $key): bool + public function has(string $key): bool { - return isset(self::$__response[$key]); + return isset($this->__response[$key]); } /** * Gets the data from response by given key * @return mixed */ - public static function get(string $key, ?string $default = null) + public function get(string $key, ?string $default = null) { - return self::has($key) ? self::$__response[$key] : $default; + return $this->has($key) ? $this->__response[$key] : $default; } /** * Sets new key/value pair into response * @param mixed $value */ - public static function set(string $key, $value): void + public function set(string $key, $value): void { - self::$__response[$key] = $value; + $this->__response[$key] = $value; } /** * Gets all response parameters * @return array */ - public static function all(): array + public function all(): array { - return self::$__response; + return $this->__response; } /** * Deletes the element from response by given key */ - public static function delete(string $key): void + public function delete(string $key): void { - if (self::has($key)) { - unset(self::$__response[$key]); + if ($this->has($key)) { + unset($this->__response[$key]); } } @@ -94,16 +94,16 @@ public static function delete(string $key): void * Prepares the JSON response * @param array|null $data */ - public static function json(?array $data = null, ?int $code = null): void + public function json(?array $data = null, ?int $code = null): void { - self::setContentType(ContentType::JSON); + $this->setContentType(ContentType::JSON); if (!is_null($code)) { - self::setStatusCode($code); + $this->setStatusCode($code); } if ($data) { - self::$__response = array_merge(self::$__response, $data); + $this->__response = array_merge($this->__response, $data); } } @@ -111,18 +111,18 @@ public static function json(?array $data = null, ?int $code = null): void * Prepares the JSONP response * @param array|null $data */ - public static function jsonp(string $callback, ?array $data = null, ?int $code = null): void + public function jsonp(string $callback, ?array $data = null, ?int $code = null): void { - self::setContentType(ContentType::JSONP); + $this->setContentType(ContentType::JSONP); - self::$callbackFunction = $callback; + $this->callbackFunction = $callback; if (!is_null($code)) { - self::setStatusCode($code); + $this->setStatusCode($code); } if ($data) { - self::$__response = array_merge(self::$__response, $data); + $this->__response = array_merge($this->__response, $data); } } @@ -130,9 +130,9 @@ public static function jsonp(string $callback, ?array $data = null, ?int $code = * Returns response with function * @param array $data */ - public static function getJsonPData(array $data): string + public function getJsonPData(array $data): string { - return self::$callbackFunction . '(' . json_encode($data) . ')'; + return $this->callbackFunction . '(' . json_encode($data) . ')'; } /** @@ -140,50 +140,50 @@ public static function getJsonPData(array $data): string * @param array|null $data * @param string $root */ - public static function xml(?array $data = null, $root = '', ?int $code = null): void + public function xml(?array $data = null, $root = '', ?int $code = null): void { - self::setContentType(ContentType::XML); + $this->setContentType(ContentType::XML); if (!is_null($code)) { - self::setStatusCode($code); + $this->setStatusCode($code); } - self::$xmlRoot = $root; + $this->xmlRoot = $root; if ($data) { - self::$__response = array_merge(self::$__response, $data); + $this->__response = array_merge($this->__response, $data); } } /** * Prepares the HTML content */ - public static function html(string $html, ?int $code = null): void + public function html(string $html, ?int $code = null): void { - self::setContentType(ContentType::HTML); + $this->setContentType(ContentType::HTML); if (!is_null($code)) { - self::setStatusCode($code); + $this->setStatusCode($code); } - self::$__response[ReservedKeys::RENDERED_VIEW] = $html; + $this->__response[ReservedKeys::RENDERED_VIEW] = $html; } /** * Gets the response content * @throws HttpException */ - public static function getContent(): string + public function getContent(): string { - $contentType = self::getContentType(); + $contentType = $this->getContentType(); - if (!isset(self::$formatters[$contentType])) { + if (!isset($this->formatters[$contentType])) { throw new HttpException("Unsupported content type: {$contentType}"); } - $formatterMethod = self::$formatters[$contentType]; + $formatterMethod = $this->formatters[$contentType]; - return self::$formatterMethod(); + return $this->$formatterMethod(); } /** @@ -191,10 +191,10 @@ public static function getContent(): string * @param array $arr * @throws Exception */ - private static function arrayToXML(array $arr): string + private function arrayToXML(array $arr): string { - $simpleXML = new SimpleXMLElement(self::$xmlRoot); - self::composeXML($arr, $simpleXML); + $simpleXML = new SimpleXMLElement($this->xmlRoot); + $this->composeXML($arr, $simpleXML); $dom = new DOMDocument(); $xml = $simpleXML->asXML(); @@ -212,7 +212,7 @@ private static function arrayToXML(array $arr): string * Compose XML * @param array $arr */ - private static function composeXML(array $arr, SimpleXMLElement &$simpleXML): void + private function composeXML(array $arr, SimpleXMLElement &$simpleXML): void { foreach ($arr as $key => $value) { if (is_numeric($key)) { @@ -235,7 +235,7 @@ private static function composeXML(array $arr, SimpleXMLElement &$simpleXML): vo } } - self::composeXML($value, $child); + $this->composeXML($value, $child); } else { $child = $simpleXML->addChild($tag, htmlspecialchars((string) $value)); @@ -251,33 +251,33 @@ private static function composeXML(array $arr, SimpleXMLElement &$simpleXML): vo /** * Formats data as JSON */ - private static function formatJson(): string + private function formatJson(): string { - return json_encode(self::all(), JSON_UNESCAPED_UNICODE) ?: ''; + return json_encode($this->all(), JSON_UNESCAPED_UNICODE) ?: ''; } /** * Formats data as XML * @throws Exception */ - private static function formatXml(): string + private function formatXml(): string { - return self::arrayToXml(self::all()); + return $this->arrayToXML($this->all()); } /** * Formats data as HTML */ - private static function formatHtml(): string + private function formatHtml(): string { - return self::get(ReservedKeys::RENDERED_VIEW) ?? ''; + return $this->get(ReservedKeys::RENDERED_VIEW) ?? ''; } /** * Formats data as JSONP */ - private static function formatJsonp(): string + private function formatJsonp(): string { - return self::getJsonPData(self::all()); + return $this->getJsonPData($this->all()); } } diff --git a/src/Http/Traits/Response/Header.php b/src/Http/Traits/Response/Header.php index 7f7abd78..d02c57cc 100644 --- a/src/Http/Traits/Response/Header.php +++ b/src/Http/Traits/Response/Header.php @@ -29,75 +29,75 @@ trait Header * Response headers * @var array */ - private static array $__headers = []; + private array $__headers = []; /** * Checks the response header existence by given key */ - public static function hasHeader(string $key): bool + public function hasHeader(string $key): bool { - return isset(self::$__headers[$key]); + return isset($this->__headers[$key]); } /** * Gets the response header by given key */ - public static function getHeader(string $key): ?string + public function getHeader(string $key): ?string { - return self::hasHeader($key) ? self::$__headers[$key] : null; + return $this->hasHeader($key) ? $this->__headers[$key] : null; } /** * Sets the response header */ - public static function setHeader(string $key, string $value): void + public function setHeader(string $key, string $value): void { - self::$__headers[$key] = $value; + $this->__headers[$key] = $value; } /** * Get all response headers * @return array */ - public static function allHeaders(): array + public function allHeaders(): array { - return self::$__headers; + return $this->__headers; } /** * Deletes the header by given key */ - public static function deleteHeader(string $key): void + public function deleteHeader(string $key): void { - if (self::hasHeader($key)) { - unset(self::$__headers[$key]); + if ($this->hasHeader($key)) { + unset($this->__headers[$key]); } } /** * Sets the content type */ - public static function setContentType(string $contentType): void + public function setContentType(string $contentType): void { - self::setHeader('Content-Type', $contentType); + $this->setHeader('Content-Type', $contentType); } /** * Gets the content type */ - public static function getContentType(): string + public function getContentType(): string { - return self::getHeader('Content-Type') ?? ContentType::HTML; + return $this->getHeader('Content-Type') ?? ContentType::HTML; } /** * Redirect * @throws StopExecutionException */ - public static function redirect(string $url, int $code = 302): void + public function redirect(string $url, int $code = 302): void { - self::setStatusCode($code); - self::setHeader('Location', $url); + $this->setStatusCode($code); + $this->setHeader('Location', $url); stop(); } } diff --git a/src/Http/Traits/Response/Status.php b/src/Http/Traits/Response/Status.php index 62cbd83e..a4c13b4a 100644 --- a/src/Http/Traits/Response/Status.php +++ b/src/Http/Traits/Response/Status.php @@ -28,13 +28,13 @@ trait Status /** * Status code */ - private static int $__statusCode = StatusCode::OK; + private int $__statusCode = StatusCode::OK; /** * Status texts * @var string[] */ - private static array $texts = [ + private array $texts = [ StatusCode::CONTINUE => 'Continue', StatusCode::SWITCHING_PROTOCOLS => 'Switching Protocols', StatusCode::PROCESSING => 'Processing', @@ -106,40 +106,40 @@ trait Status /** * Gets the reason phrase for a given HTTP status code. */ - public static function getText(int $code): string + public function getText(int $code): string { - if (!isset(self::$texts[$code])) { + if (!isset($this->texts[$code])) { throw new InvalidArgumentException("Unknown HTTP status code: {$code}"); } - return self::$texts[$code]; + return $this->texts[$code]; } /** * Sets the status code */ - public static function setStatusCode(int $code): void + public function setStatusCode(int $code): void { - if (!isset(self::$texts[$code])) { + if (!isset($this->texts[$code])) { throw new InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); } - self::$__statusCode = $code; + $this->__statusCode = $code; } /** * Gets the status code */ - public static function getStatusCode(): int + public function getStatusCode(): int { - return self::$__statusCode; + return $this->__statusCode; } /** * Gets the status text */ - public static function getStatusText(): string + public function getStatusText(): string { - return self::getText(self::$__statusCode); + return $this->getText($this->__statusCode); } } diff --git a/src/Lang/Factories/LangFactory.php b/src/Lang/Factories/LangFactory.php index 82b920e1..4367b13e 100644 --- a/src/Lang/Factories/LangFactory.php +++ b/src/Lang/Factories/LangFactory.php @@ -21,7 +21,6 @@ use Quantum\Lang\Exceptions\LangException; use Quantum\Di\Exceptions\DiException; use Quantum\Lang\Translator; -use Quantum\Http\Request; use Quantum\Loader\Setup; use ReflectionException; use Quantum\Lang\Lang; @@ -110,7 +109,7 @@ private function detectLanguage(array $supported, ?string $default): string */ private function getLangFromQuery(array $supported): ?string { - $queryLang = Request::getQueryParam('lang'); + $queryLang = request()->getQueryParam('lang'); return $queryLang && in_array($queryLang, $supported) ? $queryLang : null; } @@ -127,7 +126,7 @@ private function getLangFromUrlSegment(array $supported): ?string $segmentIndex++; } - $segmentLang = Request::getSegment($segmentIndex); + $segmentLang = request()->getSegment($segmentIndex); return $segmentLang && in_array($segmentLang, $supported) ? $segmentLang : null; } diff --git a/src/Router/Helpers/router.php b/src/Router/Helpers/router.php index d2261c2a..7b50b1bf 100644 --- a/src/Router/Helpers/router.php +++ b/src/Router/Helpers/router.php @@ -16,7 +16,6 @@ use Quantum\Environment\Environment; use Quantum\Router\RouteCollection; use Quantum\Router\Route; -use Quantum\Http\Request; use Quantum\Di\Di; /** @@ -26,8 +25,7 @@ */ function current_middlewares(): ?array { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getMiddlewares() : null; } @@ -39,8 +37,7 @@ function current_middlewares(): ?array */ function current_module(): ?string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getModule() : null; } @@ -52,8 +49,7 @@ function current_module(): ?string */ function current_controller(): ?string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getController() : null; } @@ -65,8 +61,7 @@ function current_controller(): ?string */ function current_action(): ?string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getAction() : null; } @@ -78,8 +73,7 @@ function current_action(): ?string */ function route_callback(): ?Closure { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getClosure() : null; } @@ -91,8 +85,7 @@ function route_callback(): ?Closure */ function current_route(): ?string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getPattern() : null; } @@ -104,8 +97,7 @@ function current_route(): ?string */ function route_pattern(): string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? ($matchedRoute->getRoute()->getCompiledPattern() ?? '') : ''; } @@ -117,8 +109,7 @@ function route_pattern(): string */ function route_params(): array { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getParams() : []; } @@ -142,8 +133,7 @@ function route_param(string $name) */ function route_method(): string { - $request = Di::get(Request::class); - return $request->getMethod() ?? ''; + return request()->getMethod() ?? ''; } /** @@ -153,8 +143,7 @@ function route_method(): string */ function route_uri(): ?string { - $request = Di::get(Request::class); - return $request->getUri(); + return request()->getUri(); } /** @@ -164,8 +153,7 @@ function route_uri(): ?string */ function route_cache_settings(): ?array { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getCache() : null; } @@ -176,8 +164,7 @@ function route_cache_settings(): ?array */ function route_name(): ?string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getName() : null; } @@ -188,8 +175,7 @@ function route_name(): ?string */ function route_prefix(): ?string { - $request = Di::get(Request::class); - $matchedRoute = $request->getMatchedRoute(); + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getPrefix() : null; } diff --git a/src/Tracer/ErrorHandler.php b/src/Tracer/ErrorHandler.php index 16ae139c..a91750e4 100644 --- a/src/Tracer/ErrorHandler.php +++ b/src/Tracer/ErrorHandler.php @@ -16,6 +16,7 @@ namespace Quantum\Tracer; +use Exception; use Quantum\Storage\Contracts\LocalFilesystemAdapterInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Quantum\Renderer\Exceptions\RendererException; @@ -25,7 +26,6 @@ use Quantum\Di\Exceptions\DiException; use DebugBar\DebugBarException; use Quantum\Logger\Logger; -use Quantum\Http\Response; use ReflectionException; use Psr\Log\LogLevel; use ErrorException; @@ -108,7 +108,7 @@ private function handleCliException(Throwable $throwable): void } /** - * @throws ConfigException|RendererException|DiException|BaseException|ReflectionException + * @throws ConfigException|RendererException|DiException|BaseException|ReflectionException|Exception */ private function handleWebException(Throwable $throwable): void { @@ -126,8 +126,8 @@ private function handleWebException(Throwable $throwable): void $this->logError($throwable, $errorType); } - Response::html($errorPage); - Response::send(); + response()->html($errorPage); + response()->send(); } private function logError(Throwable $e, string $errorType): void diff --git a/tests/Unit/App/Adapters/WebAppAdapterTest.php b/tests/Unit/App/Adapters/WebAppAdapterTest.php index 4ce61246..20a4431a 100644 --- a/tests/Unit/App/Adapters/WebAppAdapterTest.php +++ b/tests/Unit/App/Adapters/WebAppAdapterTest.php @@ -4,7 +4,6 @@ use Quantum\App\Adapters\WebAppAdapter; use PHPUnit\Framework\TestCase; -use Quantum\Http\Request; use Quantum\App\App; use Quantum\Di\Di; @@ -27,8 +26,7 @@ public function tearDown(): void public function testWebAppAdapterStartSuccessfully(): void { - $request = Di::get(Request::class); - $request->create('GET', '/test/am/tests'); + request()->create('GET', '/test/am/tests'); ob_start(); $result = $this->webAppAdapter->start(); @@ -39,8 +37,7 @@ public function testWebAppAdapterStartSuccessfully(): void public function testWebAppAdapterStartFails(): void { - $request = Di::get(Request::class); - $request->create('POST', ''); + request()->create('POST', ''); ob_start(); $result = $this->webAppAdapter->start(); @@ -51,8 +48,7 @@ public function testWebAppAdapterStartFails(): void public function testWebAppAdapterHandlesPageNotFoundGracefully(): void { - $request = Di::get(Request::class); - $request->create('GET', '/non-existing-uri'); + request()->create('GET', '/non-existing-uri'); ob_start(); $result = $this->webAppAdapter->start(); diff --git a/tests/Unit/App/AppTest.php b/tests/Unit/App/AppTest.php index 28e26c5f..4b37cde7 100644 --- a/tests/Unit/App/AppTest.php +++ b/tests/Unit/App/AppTest.php @@ -7,7 +7,6 @@ use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Contracts\AppInterface; use PHPUnit\Framework\TestCase; -use Quantum\Http\Request; use Quantum\App\App; use Quantum\Di\Di; @@ -52,8 +51,7 @@ public function testAppCallingValidMethod(): void { $app = new App(new WebAppAdapter()); - $request = Di::get(Request::class); - $request->create('GET', '/test/am/tests'); + request()->create('GET', '/test/am/tests'); ob_start(); $this->assertEquals(0, $app->start()); diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 8afee0a0..6a201cff 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -11,7 +11,6 @@ use Quantum\App\Enums\AppType; use Quantum\Config\Config; use Quantum\Router\Route; -use Quantum\Http\Request; use Quantum\Loader\Setup; use ReflectionClass; use Quantum\Di\Di; @@ -33,8 +32,8 @@ public function setUp(): void public function tearDown(): void { - Request::setMatchedRoute(null); - Request::flush(); + request()->setMatchedRoute(null); + request()->flush(); AppFactory::destroy(AppType::WEB); @@ -81,9 +80,7 @@ protected function testRequest( array $body = [], array $headers = [] ) { - $request = new Request(); - - $request->create($method, $uri, $body, $headers); + request()->create($method, $uri, $body, $headers); $route = new Route( [$method], @@ -94,10 +91,6 @@ protected function testRequest( $route->module('Test'); $matchedRoute = new MatchedRoute($route, []); - Request::setMatchedRoute($matchedRoute); - - if (!Di::isRegistered(Request::class)) { - Di::set(Request::class, $request); - } + request()->setMatchedRoute($matchedRoute); } } diff --git a/tests/Unit/Csrf/CsrfTest.php b/tests/Unit/Csrf/CsrfTest.php index 26178ace..74367dea 100644 --- a/tests/Unit/Csrf/CsrfTest.php +++ b/tests/Unit/Csrf/CsrfTest.php @@ -18,7 +18,7 @@ public function setUp(): void { parent::setUp(); - $this->request = new Request(); + $this->request = request(); $this->csrf = csrf(); } diff --git a/tests/Unit/Http/Helpers/HttpHelperTest.php b/tests/Unit/Http/Helpers/HttpHelperTest.php index c19f44db..8cac6966 100644 --- a/tests/Unit/Http/Helpers/HttpHelperTest.php +++ b/tests/Unit/Http/Helpers/HttpHelperTest.php @@ -22,16 +22,25 @@ public function setUp(): void { parent::setUp(); - $this->request = new Request(); + $this->request = request(); + $this->response = response(); + } - Response::init(); + public function tearDown(): void + { + request()->flush(); + } - $this->response = new Response(); + public function testRequestHelperReturnsDiInstance(): void + { + $this->assertInstanceOf(Request::class, request()); + $this->assertSame(request(), request()); } - public function tearDown(): void + public function testResponseHelperReturnsDiInstance(): void { - Request::flush(); + $this->assertInstanceOf(Response::class, response()); + $this->assertSame(response(), response()); } public function testBaseUrlWithoutModulePrefix(): void @@ -67,14 +76,9 @@ public function testBaseUrlWithModulePrefix(): void $this->request->create('GET', 'https://testdomain.com/signin'); - // Register request in DI for route finding (only if not already registered) - if (!Di::isRegistered(Request::class)) { - Di::set(Request::class, $this->request); - } - $matchedRoute = $router->find($this->request); - Request::setMatchedRoute($matchedRoute); + request()->setMatchedRoute($matchedRoute); $baseUrl = base_url(true); diff --git a/tests/Unit/Http/RequestTest.php b/tests/Unit/Http/RequestTest.php index e238a6d5..35caf7c7 100644 --- a/tests/Unit/Http/RequestTest.php +++ b/tests/Unit/Http/RequestTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Request; class RequestTest extends AppTestCase { @@ -15,12 +14,12 @@ public function setUp(): void public function tearDown(): void { - Request::flush(); + request()->flush(); } public function testSetGetMethod(): void { - $request = new Request(); + $request = request(); $request->create('GET', '/'); @@ -33,7 +32,7 @@ public function testSetGetMethod(): void public function testIsMethod(): void { - $request = new Request(); + $request = request(); $request->create('GET', '/'); @@ -52,7 +51,7 @@ public function testIsMethod(): void public function testGetCsrfToken(): void { - $request = new Request(); + $request = request(); $this->assertNull($request->getCsrfToken()); diff --git a/tests/Unit/Http/ResponseTest.php b/tests/Unit/Http/ResponseTest.php index b4f5aed7..251f2eea 100644 --- a/tests/Unit/Http/ResponseTest.php +++ b/tests/Unit/Http/ResponseTest.php @@ -4,7 +4,6 @@ use Quantum\Http\Enums\ContentType; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Response; use Throwable; class ResponseTest extends AppTestCase @@ -12,18 +11,16 @@ class ResponseTest extends AppTestCase public function setUp(): void { parent::setUp(); - - Response::init(); } public function tearDown(): void { - Response::flush(); + response()->flush(); } public function testResponseContentType(): void { - $response = new Response(); + $response = response(); $this->assertEquals(ContentType::HTML, $response->getContentType()); @@ -34,7 +31,7 @@ public function testResponseContentType(): void public function testResponseRedirect(): void { - $response = new Response(); + $response = response(); $this->assertFalse($response->hasHeader('Location')); diff --git a/tests/Unit/Http/Traits/Request/HttpRawInputTest.php b/tests/Unit/Http/Traits/Request/HttpRawInputTest.php index d769e30c..0b0adc9f 100644 --- a/tests/Unit/Http/Traits/Request/HttpRawInputTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRawInputTest.php @@ -4,7 +4,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\UploadedFile; -use Quantum\Http\Request; class HttpRawInputTest extends AppTestCase { @@ -22,7 +21,8 @@ public function testParseReturnsEmptyWhenNoBoundary(): void { server()->set('CONTENT_TYPE', null); - $result = Request::parse('irrelevant-body'); + $request = request(); + $result = $request->parse('irrelevant-body'); $this->assertEquals(['params' => [], 'files' => []], $result); } @@ -38,7 +38,8 @@ public function testParseWithParameterBlock(): void server()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); - $result = Request::parse($rawInput); + $request = request(); + $result = $request->parse($rawInput); $this->assertArrayHasKey('params', $result); @@ -58,7 +59,8 @@ public function testParseWithStreamBlock(): void server()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); - $result = Request::parse($rawInput); + $request = request(); + $result = $request->parse($rawInput); $this->assertArrayHasKey('params', $result); @@ -81,7 +83,8 @@ public function testParseWithFileBlock(): void server()->set('CONTENT_TYPE', "multipart/form-data; boundary=$boundary"); - $result = Request::parse($rawInput); + $request = request(); + $result = $request->parse($rawInput); $this->assertArrayHasKey('files', $result); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php b/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php index cc3c809a..daf9eaa2 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestBodyTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Request; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Request; class HttpRequestBodyTest extends AppTestCase { @@ -14,12 +13,12 @@ public function setUp(): void public function tearDown(): void { - Request::flush(); + request()->flush(); } public function testRequestSetHasGetDelete(): void { - $request = new Request(); + $request = request(); $this->assertFalse($request->has('name')); @@ -52,7 +51,7 @@ public function testRequestSetHasGetDelete(): void public function testRequestAll(): void { - $request = new Request(); + $request = request(); $this->assertEmpty($request->all()); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php b/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php index 3ffcdde0..79d306c2 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestFileTest.php @@ -5,7 +5,6 @@ use Quantum\Storage\Exceptions\FileUploadException; use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\UploadedFile; -use Quantum\Http\Request; class HttpRequestFileTest extends AppTestCase { @@ -16,12 +15,12 @@ public function setUp(): void public function tearDown(): void { - Request::flush(); + request()->flush(); } public function testHasGetFile(): void { - $request = new Request(); + $request = request(); $file = [ 'image' => [ @@ -62,7 +61,7 @@ public function testHasGetFile(): void public function testGetMultipleFiles(): void { - $request = new Request(); + $request = request(); $this->assertFalse($request->hasFile('image')); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php b/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php index ae418a4e..3553cb77 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestHeaderTest.php @@ -3,27 +3,24 @@ namespace Quantum\Tests\Unit\Http\Traits\Request; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Request; class HttpRequestHeaderTest extends AppTestCase { public function setUp(): void { parent::setUp(); - - Request::init(server()); } public function tearDown(): void { - Request::flush(); + request()->flush(); server()->flush(); } public function testRequestHeaderSetHasGetDelete(): void { - $request = new Request(); + $request = request(); $this->assertFalse($request->hasHeader('name')); @@ -40,7 +37,7 @@ public function testRequestHeaderSetHasGetDelete(): void public function testRequestHeaderAll(): void { - $request = new Request(); + $request = request(); $this->assertEmpty($request->allHeaders()); @@ -53,7 +50,7 @@ public function testRequestHeaderAll(): void public function testGetAuthorizationBearer(): void { - $request = new Request(); + $request = request(); $bearerToken = md5('random'); @@ -68,7 +65,7 @@ public function testGetAuthorizationBearer(): void public function testGetBasicAuthCredentialsFromServer(): void { - $request = new Request(); + $request = request(); $server = server(); @@ -94,7 +91,7 @@ public function testGetBasicAuthCredentialsFromServer(): void public function testGetBasicAuthCredentialsFromHeader(): void { - $request = new Request(); + $request = request(); $username = 'testHeaderName'; @@ -117,7 +114,7 @@ public function testGetBasicAuthCredentialsFromHeader(): void public function testIsAjaxReturnsTrueWhenHeaderIsSet(): void { - $request = new Request(); + $request = request(); $this->assertFalse($request->isAjax()); @@ -128,7 +125,7 @@ public function testIsAjaxReturnsTrueWhenHeaderIsSet(): void public function testIsAjaxReturnsTrueFromServerWhenHeaderIsMissing(): void { - $request = new Request(); + $request = request(); $this->assertFalse($request->isAjax()); @@ -139,12 +136,12 @@ public function testIsAjaxReturnsTrueFromServerWhenHeaderIsMissing(): void public function testGetReferrer(): void { - $this->assertNull(Request::getReferrer()); + $this->assertNull(request()->getReferrer()); $referrer = 'https://example.com/page'; server()->set('HTTP_REFERER', $referrer); - $this->assertEquals($referrer, Request::getReferrer()); + $this->assertEquals($referrer, request()->getReferrer()); } } diff --git a/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php b/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php index cbc35399..a44ba07a 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestInternalTest.php @@ -4,7 +4,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Storage\UploadedFile; -use Quantum\Http\Request; class HttpRequestInternalTest extends AppTestCase { @@ -15,7 +14,8 @@ public function setUp(): void public function testCreateRequestSetsBasicServerParams(): void { - Request::create('GET', 'https://example.com/test/path?foo=bar'); + $request = request(); + $request->create('GET', 'https://example.com/test/path?foo=bar'); $server = server(); @@ -41,21 +41,24 @@ public function testContentTypeIsMultipartWhenFilesProvided(): void ], ]; - Request::create('POST', 'http://localhost/upload', [], [], $files); + $request = request(); + $request->create('POST', 'http://localhost/upload', [], [], $files); $this->assertEquals('multipart/form-data', server()->get('CONTENT_TYPE')); } public function testContentTypeIsFormUrlencodedWhenDataProvided(): void { - Request::create('POST', 'http://localhost/form', ['key' => 'value']); + $request = request(); + $request->create('POST', 'http://localhost/form', ['key' => 'value']); $this->assertEquals('application/x-www-form-urlencoded', server()->get('CONTENT_TYPE')); } public function testContentTypeIsTextHtmlWhenNoDataOrFiles(): void { - Request::create('GET', 'http://localhost'); + $request = request(); + $request->create('GET', 'http://localhost'); $this->assertEquals('text/html', server()->get('CONTENT_TYPE')); } @@ -64,9 +67,10 @@ public function testRequestParamsAreSet(): void { $data = ['foo' => 'bar']; - Request::create('POST', 'http://localhost/submit', $data); + $request = request(); + $request->create('POST', 'http://localhost/submit', $data); - $this->assertEquals('bar', Request::get('foo')); + $this->assertEquals('bar', $request->get('foo')); } public function testUploadedFilesAreSet(): void @@ -81,7 +85,7 @@ public function testUploadedFilesAreSet(): void ], ]; - $request = new Request(); + $request = request(); $request->create('POST', 'http://localhost/upload', [], [], $files); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php b/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php index d1ae96cf..e31e97d8 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestQueryTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Request; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Request; class HttpRequestQueryTest extends AppTestCase { @@ -14,12 +13,12 @@ public function setUp(): void public function tearDown(): void { - Request::flush(); + request()->flush(); } public function testSetGetQuery(): void { - $request = new Request(); + $request = request(); $request->create('GET', 'http://test.com:8080/user?firstname=john&lastname=doe'); @@ -36,7 +35,7 @@ public function testSetGetQuery(): void public function testSetGetQueryParam(): void { - $request = new Request(); + $request = request(); $request->setQueryParam('name', 'John'); diff --git a/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php b/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php index 1c8efcf1..7d1c8661 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestRouteTest.php @@ -4,29 +4,21 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Router\MatchedRoute; -use Quantum\Http\Request; use Quantum\Router\Route; -use Quantum\Di\Di; class HttpRequestRouteTest extends AppTestCase { public function tearDown(): void { - Request::setMatchedRoute(null); - Request::flush(); - - if (Di::isRegistered(Request::class)) { - $diRequest = Di::get(Request::class); - $diRequest->setMatchedRoute(null); - $diRequest->flush(); - } + request()->setMatchedRoute(null); + request()->flush(); parent::tearDown(); } public function testGetMatchedRouteReturnsNullByDefault(): void { - $this->assertNull(Request::getMatchedRoute()); + $this->assertNull(request()->getMatchedRoute()); } public function testSetAndGetMatchedRoute(): void @@ -40,9 +32,9 @@ public function testSetAndGetMatchedRoute(): void $matched = new MatchedRoute($route, ['id' => 1]); - Request::setMatchedRoute($matched); + request()->setMatchedRoute($matched); - $this->assertSame($matched, Request::getMatchedRoute()); + $this->assertSame($matched, request()->getMatchedRoute()); } public function testSetMatchedRouteToNullResetsState(): void @@ -56,10 +48,10 @@ public function testSetMatchedRouteToNullResetsState(): void $matched = new MatchedRoute($route, []); - Request::setMatchedRoute($matched); - $this->assertNotNull(Request::getMatchedRoute()); + request()->setMatchedRoute($matched); + $this->assertNotNull(request()->getMatchedRoute()); - Request::setMatchedRoute(null); - $this->assertNull(Request::getMatchedRoute()); + request()->setMatchedRoute(null); + $this->assertNull(request()->getMatchedRoute()); } } diff --git a/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php b/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php index 026ed0e7..6e0fb70e 100644 --- a/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php +++ b/tests/Unit/Http/Traits/Request/HttpRequestUrlTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Request; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Request; class HttpRequestUrlTest extends AppTestCase { @@ -14,12 +13,12 @@ public function setUp(): void public function tearDown(): void { - Request::flush(); + request()->flush(); } public function testSetGetProtocol(): void { - $request = new Request(); + $request = request(); $request->create('GET', 'https://test.com'); @@ -32,7 +31,7 @@ public function testSetGetProtocol(): void public function testSetGetHost(): void { - $request = new Request(); + $request = request(); $request->create('GET', 'https://test.com/dashboard'); @@ -45,7 +44,7 @@ public function testSetGetHost(): void public function testSetGetPort(): void { - $request = new Request(); + $request = request(); $request->create('GET', 'https://test.com:8080/dashboard'); @@ -58,7 +57,7 @@ public function testSetGetPort(): void public function testSetGetUri(): void { - $request = new Request(); + $request = request(); $request->create('GET', 'http://test.com/post/12'); @@ -71,7 +70,7 @@ public function testSetGetUri(): void public function testGetSegments(): void { - $request = new Request(); + $request = request(); $request->create('GET', 'post/12/notes'); diff --git a/tests/Unit/Http/Traits/Response/HttpResponseBodyTest.php b/tests/Unit/Http/Traits/Response/HttpResponseBodyTest.php index e32beb60..829c503a 100644 --- a/tests/Unit/Http/Traits/Response/HttpResponseBodyTest.php +++ b/tests/Unit/Http/Traits/Response/HttpResponseBodyTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Response; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Response; class HttpResponseBodyTest extends AppTestCase { @@ -14,12 +13,12 @@ public function setUp(): void public function tearDown(): void { - Response::flush(); + response()->flush(); } public function testResponseSetHasGetAllDelete(): void { - $response = new Response(); + $response = response(); $this->assertEmpty($response->all()); @@ -44,7 +43,7 @@ public function testResponseSetHasGetAllDelete(): void public function testResponseJsonContent(): void { - $response = new Response(); + $response = response(); $response->set('firstname', 'John'); @@ -70,7 +69,7 @@ public function testResponseJsonContent(): void public function testResponseJsonP(): void { - $response = new Response(); + $response = response(); $response->set('firstname', 'John'); @@ -83,7 +82,7 @@ public function testResponseJsonP(): void public function testResponseXmlContent(): void { - $response = new Response(); + $response = response(); $response->set('firstname', 'John'); @@ -129,7 +128,7 @@ public function testResponseXmlContent(): void public function testResponseXmlWithNestedArray(): void { - $response = new Response(); + $response = response(); $response->xml([ 'article' => [ @@ -151,7 +150,7 @@ public function testResponseXmlWithNestedArray(): void public function testResponseXmlWithArguments(): void { - $response = new Response(); + $response = response(); $response->xml([ 'article@{"type":"post"}' => [ @@ -173,7 +172,7 @@ public function testResponseXmlWithArguments(): void public function testResponseXmlWithCustomRoot(): void { - $response = new Response(); + $response = response(); $response->xml([ 'article@{"type":"post"}' => [ @@ -195,7 +194,7 @@ public function testResponseXmlWithCustomRoot(): void public function testResponseHtmlContent(): void { - $response = new Response(); + $response = response(); $response->html('
John Doe
'); diff --git a/tests/Unit/Http/Traits/Response/HttpResponseHeaderTest.php b/tests/Unit/Http/Traits/Response/HttpResponseHeaderTest.php index 71f61a8a..bb1cb023 100644 --- a/tests/Unit/Http/Traits/Response/HttpResponseHeaderTest.php +++ b/tests/Unit/Http/Traits/Response/HttpResponseHeaderTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Response; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Response; class HttpResponseHeaderTest extends AppTestCase { @@ -15,7 +14,7 @@ public function setUp(): void public function testResponseHeaderSetHasGetAllDelete(): void { - $response = new Response(); + $response = response(); $this->assertEmpty($response->allHeaders()); diff --git a/tests/Unit/Http/Traits/Response/HttpResponseStatusTest.php b/tests/Unit/Http/Traits/Response/HttpResponseStatusTest.php index 1f35302c..a92919db 100644 --- a/tests/Unit/Http/Traits/Response/HttpResponseStatusTest.php +++ b/tests/Unit/Http/Traits/Response/HttpResponseStatusTest.php @@ -3,7 +3,6 @@ namespace Quantum\Tests\Unit\Http\Traits\Response; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Response; class HttpResponseStatusTest extends AppTestCase { @@ -14,7 +13,7 @@ public function setUp(): void public function testResponseStatus(): void { - $response = new Response(); + $response = response(); $this->assertEquals(200, $response->getStatusCode()); @@ -27,7 +26,7 @@ public function testResponseStatus(): void public function testHttpStatusGetText(): void { - $response = new Response(); + $response = response(); $this->assertEquals('OK', $response->getText(200)); diff --git a/tests/Unit/Lang/LangTest.php b/tests/Unit/Lang/LangTest.php index 35141829..18ddf628 100644 --- a/tests/Unit/Lang/LangTest.php +++ b/tests/Unit/Lang/LangTest.php @@ -6,9 +6,7 @@ use Quantum\Router\MatchedRoute; use Quantum\Lang\Translator; use Quantum\Router\Route; -use Quantum\Http\Request; use Quantum\Lang\Lang; -use Quantum\Di\Di; class LangTest extends AppTestCase { @@ -30,13 +28,9 @@ public function setUp(): void $route->module('Test'); $matchedRoute = new MatchedRoute($route, []); - Request::setMatchedRoute($matchedRoute); - if (!Di::isRegistered(Request::class)) { - $request = new Request(); - $request->create('POST', '/api-signin'); - Di::set(Request::class, $request); - } + request()->create('POST', '/api-signin'); + request()->setMatchedRoute($matchedRoute); } public function testLangGetSet(): void diff --git a/tests/Unit/Module/ModuleManagerTest.php b/tests/Unit/Module/ModuleManagerTest.php index e01e9e64..84c4d132 100644 --- a/tests/Unit/Module/ModuleManagerTest.php +++ b/tests/Unit/Module/ModuleManagerTest.php @@ -4,7 +4,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Module\ModuleManager; -use Quantum\Http\Response; use Quantum\App\App; use Exception; use Mockery; @@ -38,7 +37,7 @@ public function testCreateModule(): void $mainController = new \Quantum\Tests\_root\modules\Api\Controllers\MainController(); - $response = new Response(); + $response = response(); $mainController->index($response); diff --git a/tests/Unit/Renderer/Adapters/HtmlAdapterTest.php b/tests/Unit/Renderer/Adapters/HtmlAdapterTest.php index f76bb90b..8ee38be6 100644 --- a/tests/Unit/Renderer/Adapters/HtmlAdapterTest.php +++ b/tests/Unit/Renderer/Adapters/HtmlAdapterTest.php @@ -6,8 +6,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Router\MatchedRoute; use Quantum\Router\Route; -use Quantum\Http\Request; -use Quantum\Di\Di; class HtmlAdapterTest extends AppTestCase { @@ -25,9 +23,8 @@ public function setUp(): void $matchedRoute = new MatchedRoute($route, []); - $request = Di::get(Request::class); - $request->create('GET', '/test'); - Request::setMatchedRoute($matchedRoute); + request()->create('GET', '/test'); + request()->setMatchedRoute($matchedRoute); } public function testHtmlAdapterRenderView(): void diff --git a/tests/Unit/Renderer/Adapters/TwigAdapterTest.php b/tests/Unit/Renderer/Adapters/TwigAdapterTest.php index 7d7e401d..0cb032e0 100644 --- a/tests/Unit/Renderer/Adapters/TwigAdapterTest.php +++ b/tests/Unit/Renderer/Adapters/TwigAdapterTest.php @@ -6,8 +6,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Router\MatchedRoute; use Quantum\Router\Route; -use Quantum\Http\Request; -use Quantum\Di\Di; class TwigAdapterTest extends AppTestCase { @@ -24,11 +22,9 @@ public function setUp(): void $route->module('Test'); $matchedRoute = new MatchedRoute($route, []); - Request::setMatchedRoute($matchedRoute); - $request = Di::get(Request::class); - $request->create('GET', '/test'); - Request::setMatchedRoute($matchedRoute); + request()->create('GET', '/test'); + request()->setMatchedRoute($matchedRoute); } public function testHtmlAdapterRenderView(): void diff --git a/tests/Unit/Renderer/RendererTest.php b/tests/Unit/Renderer/RendererTest.php index 7237a98c..f0dee936 100644 --- a/tests/Unit/Renderer/RendererTest.php +++ b/tests/Unit/Renderer/RendererTest.php @@ -10,8 +10,6 @@ use Quantum\Router\MatchedRoute; use Quantum\Renderer\Renderer; use Quantum\Router\Route; -use Quantum\Http\Request; -use Quantum\Di\Di; class RendererTest extends AppTestCase { @@ -28,13 +26,9 @@ public function setUp(): void $route->module('Test'); $matchedRoute = new MatchedRoute($route, []); - Request::setMatchedRoute($matchedRoute); - if (!Di::isRegistered(Request::class)) { - $request = new Request(); - $request->create('GET', '/test'); - Di::set(Request::class, $request); - } + request()->create('GET', '/test'); + request()->setMatchedRoute($matchedRoute); } public function testRendererGetHtmlAdapter(): void diff --git a/tests/Unit/ResourceCache/ViewCacheTest.php b/tests/Unit/ResourceCache/ViewCacheTest.php index 99d56b16..eaeca9fd 100644 --- a/tests/Unit/ResourceCache/ViewCacheTest.php +++ b/tests/Unit/ResourceCache/ViewCacheTest.php @@ -5,7 +5,6 @@ use Quantum\ResourceCache\Exceptions\ResourceCacheException; use Quantum\ResourceCache\ViewCache; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Http\Response; use Quantum\Di\Di; class ViewCacheDouble extends ViewCache @@ -61,7 +60,7 @@ public function testServeCachedView(): void $this->viewCache->set($this->route, $this->content); - $response = new Response(); + $response = response(); $result = $this->viewCache->serveCachedView($this->route, $response); diff --git a/tests/Unit/Router/Helpers/RouteHelpersTest.php b/tests/Unit/Router/Helpers/RouteHelpersTest.php index 47ca6126..b99d0a27 100644 --- a/tests/Unit/Router/Helpers/RouteHelpersTest.php +++ b/tests/Unit/Router/Helpers/RouteHelpersTest.php @@ -6,21 +6,14 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\Router\MatchedRoute; use Quantum\Router\Route; -use Quantum\Http\Request; use Quantum\Di\Di; class RouteHelpersTest extends AppTestCase { public function tearDown(): void { - Request::setMatchedRoute(null); - Request::flush(); - - if (Di::isRegistered(Request::class)) { - $diRequest = Di::get(Request::class); - $diRequest->setMatchedRoute(null); - $diRequest->flush(); - } + request()->setMatchedRoute(null); + request()->flush(); parent::tearDown(); } @@ -64,7 +57,7 @@ public function testHelpersReturnValuesForMatchedControllerRoute(): void ['uuid' => 'abc-123'] ); - Request::setMatchedRoute($matched); + request()->setMatchedRoute($matched); $this->assertSame(['Auth', 'Editor'], current_middlewares()); $this->assertSame('Web', current_module()); @@ -94,7 +87,7 @@ public function testRouteCallbackReturnsClosureForClosureRoute(): void $matched = new MatchedRoute($route, []); - Request::setMatchedRoute($matched); + request()->setMatchedRoute($matched); $this->assertSame($closure, route_callback()); $this->assertNull(current_controller()); @@ -146,8 +139,7 @@ public function testRouteGroupExistsDetectsGroupInModule(): void public function testRouteMethodAndUri(): void { - $request = new Request(); - $request->create('POST', 'http://example.com/api/test'); + request()->create('POST', 'http://example.com/api/test'); $this->assertSame('POST', route_method()); diff --git a/tests/Unit/Router/RouteFinderTest.php b/tests/Unit/Router/RouteFinderTest.php index f81d474d..43794303 100644 --- a/tests/Unit/Router/RouteFinderTest.php +++ b/tests/Unit/Router/RouteFinderTest.php @@ -6,7 +6,6 @@ use Quantum\Router\RouteCollection; use Quantum\Router\MatchedRoute; use Quantum\Router\RouteFinder; -use Quantum\Http\Request; use Quantum\Router\Route; class RouteFinderTest extends AppTestCase @@ -27,7 +26,7 @@ public function testRouteFinderFindReturnsMatchedRouteForStaticMatch(): void $route = new Route(['GET'], 'users', 'Ctrl', 'act'); $this->collection->add($route); - $req = new Request(); + $req = request(); $req->create('GET', '/users'); $result = $this->finder->find($req); @@ -42,7 +41,7 @@ public function testRouteFinderFindReturnsNullWhenNoMatch(): void $route = new Route(['GET'], 'users', 'Ctrl', 'act'); $this->collection->add($route); - $req = new Request(); + $req = request(); $req->create('GET', '/posts'); $this->assertNull($this->finder->find($req)); @@ -53,7 +52,7 @@ public function testRouteFinderFindSkipsWrongHttpMethod(): void $route = new Route(['POST'], 'users', 'Ctrl', 'act'); $this->collection->add($route); - $req = new Request(); + $req = request(); $req->create('GET', '/users'); $this->assertNull($this->finder->find($req)); @@ -67,7 +66,7 @@ public function testRouteFinderFindReturnsFirstMatchingRouteOnly(): void $this->collection->add($r1); $this->collection->add($r2); - $req = new Request(); + $req = request(); $req->create('GET', '/users'); $result = $this->finder->find($req); @@ -80,7 +79,7 @@ public function testRouteFinderFindPassesExtractedParams(): void $route = new Route(['GET'], 'users/[id=:num]', 'Ctrl', 'act'); $this->collection->add($route); - $req = new Request(); + $req = request(); $req->create('GET', '/users/42'); $result = $this->finder->find($req); @@ -96,7 +95,7 @@ public function testRouteFinderFindWithMultipleRoutesOnlyOneMatches(): void $r2 = new Route(['GET'], 'users', 'C', 'a'); $this->collection->add($r2); - $req = new Request(); + $req = request(); $req->create('GET', '/users'); $result = $this->finder->find($req); diff --git a/tests/Unit/Validation/Traits/FileRuleTest.php b/tests/Unit/Validation/Traits/FileRuleTest.php index 4a3d3ccc..ed86d879 100644 --- a/tests/Unit/Validation/Traits/FileRuleTest.php +++ b/tests/Unit/Validation/Traits/FileRuleTest.php @@ -16,7 +16,7 @@ public function setUp(): void { parent::setUp(); - $this->request = new Request(); + $this->request = request(); $this->validator = new Validator(); diff --git a/tests/Unit/View/Helpers/ViewHelperTest.php b/tests/Unit/View/Helpers/ViewHelperTest.php index ca3e0790..a03277d3 100644 --- a/tests/Unit/View/Helpers/ViewHelperTest.php +++ b/tests/Unit/View/Helpers/ViewHelperTest.php @@ -5,8 +5,6 @@ use Quantum\View\Factories\ViewFactory; use Quantum\Router\MatchedRoute; use Quantum\Router\Route; -use Quantum\Http\Request; -use Quantum\Di\Di; use Quantum\Tests\Unit\AppTestCase; use Quantum\View\QtView; @@ -27,11 +25,9 @@ public function setUp(): void $route->module('Test'); $matchedRoute = new MatchedRoute($route, []); - Request::setMatchedRoute($matchedRoute); - $request = Di::get(Request::class); - $request->create('POST', '/test'); - Request::setMatchedRoute($matchedRoute); + request()->create('POST', '/test'); + request()->setMatchedRoute($matchedRoute); $this->view = ViewFactory::get(); } diff --git a/tests/Unit/View/QtViewTest.php b/tests/Unit/View/QtViewTest.php index ebe76c6e..a2047dcd 100644 --- a/tests/Unit/View/QtViewTest.php +++ b/tests/Unit/View/QtViewTest.php @@ -9,7 +9,6 @@ use Quantum\View\QtView; use Quantum\View\RawParam; use Quantum\Router\Route; -use Quantum\Http\Request; use Quantum\Di\Di; class QtViewTest extends AppTestCase @@ -29,11 +28,9 @@ public function setUp(): void $route->module('Test'); $matchedRoute = new MatchedRoute($route, []); - Request::setMatchedRoute($matchedRoute); - $request = Di::get(Request::class); - $request->create('GET', '/test'); - Request::setMatchedRoute($matchedRoute); + request()->create('GET', '/test'); + request()->setMatchedRoute($matchedRoute); $this->view = ViewFactory::get(); } From 7daac465b81accaced1b56c00ef3883dfd981e20 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:52:28 +0400 Subject: [PATCH 46/77] [#454] Replace static Request calls with request() helper in DemoWeb templates --- .../Templates/DemoWeb/src/Controllers/BaseController.php.tpl | 3 +-- .../Templates/DemoWeb/src/Controllers/PostController.php.tpl | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Module/Templates/DemoWeb/src/Controllers/BaseController.php.tpl b/src/Module/Templates/DemoWeb/src/Controllers/BaseController.php.tpl index 6fcecf55..941a3e66 100644 --- a/src/Module/Templates/DemoWeb/src/Controllers/BaseController.php.tpl +++ b/src/Module/Templates/DemoWeb/src/Controllers/BaseController.php.tpl @@ -16,7 +16,6 @@ namespace {{MODULE_NAMESPACE}}\Controllers; use Quantum\View\Factories\ViewFactory; use Quantum\Asset\Asset; -use Quantum\Http\Request; use Quantum\View\QtView; /** @@ -33,7 +32,7 @@ abstract class BaseController public function __before() { - if (Request::isMethod('get')) { + if (request()->isMethod('get')) { $this->view = ViewFactory::get(); $this->view->setLayout(static::LAYOUT, [ diff --git a/src/Module/Templates/DemoWeb/src/Controllers/PostController.php.tpl b/src/Module/Templates/DemoWeb/src/Controllers/PostController.php.tpl index 26135179..a00b8788 100644 --- a/src/Module/Templates/DemoWeb/src/Controllers/PostController.php.tpl +++ b/src/Module/Templates/DemoWeb/src/Controllers/PostController.php.tpl @@ -72,7 +72,7 @@ class PostController extends BaseController 'title' => t('common.posts') . ' | ' . config()->get('app.name'), 'posts' => $this->postService->transformData($paginatedPosts->data()->all()), 'pagination' => $paginatedPosts, - 'referer' => nav_ref_encode(Request::getQuery()) + 'referer' => nav_ref_encode(request()->getQuery()) ]); $response->html($this->view->render('post/post')); From bfd360ad6a6dafa78e2d3fefaed297a2018e70e2 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:15:40 +0400 Subject: [PATCH 47/77] [#455] Create instance-based DiContainer class extracted from Di --- src/Di/DiContainer.php | 327 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 src/Di/DiContainer.php diff --git a/src/Di/DiContainer.php b/src/Di/DiContainer.php new file mode 100644 index 00000000..3eebbfad --- /dev/null +++ b/src/Di/DiContainer.php @@ -0,0 +1,327 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\Di; + +use Quantum\Di\Exceptions\DiException; +use ReflectionException; +use ReflectionParameter; +use ReflectionFunction; +use ReflectionMethod; +use ReflectionClass; +use Closure; + +/** + * DiContainer Class + * + * Instance-based dependency injection container. + * Holds all dependency registrations and resolved instances for a single application execution. + * + * @package Quantum/Di + */ +class DiContainer +{ + /** + * @var array + */ + private array $dependencies = []; + + /** + * @var array + */ + private array $container = []; + + /** + * @var array + */ + private array $resolving = []; + + /** + * Register dependencies + * @param array $dependencies + * @throws DiException + */ + public function registerDependencies(array $dependencies): void + { + foreach ($dependencies as $abstract => $concrete) { + if (!$this->isRegistered($abstract)) { + $this->register($concrete, $abstract); + } + } + } + + /** + * Registers new dependency + * @throws DiException + */ + public function register(string $concrete, ?string $abstract = null): void + { + $key = $abstract ?? $concrete; + + if (isset($this->dependencies[$key])) { + throw DiException::dependencyAlreadyRegistered($key); + } + + if (!class_exists($concrete)) { + throw DiException::dependencyNotInstantiable($concrete); + } + + if ($abstract !== null && !class_exists($abstract) && !interface_exists($abstract)) { + throw DiException::invalidAbstractDependency($abstract); + } + + $this->dependencies[$key] = $concrete; + } + + /** + * Checks if a dependency registered + */ + public function isRegistered(string $abstract): bool + { + return isset($this->dependencies[$abstract]); + } + + /** + * Checks if an instance exists in the container + */ + public function has(string $abstract): bool + { + return isset($this->container[$abstract]); + } + + /** + * Sets an instance into container + * @template T of object + * @param class-string $abstract + * @param T $instance + * @throws DiException + */ + public function set(string $abstract, object $instance, bool $override = true): void + { + if (!class_exists($abstract) && !interface_exists($abstract)) { + throw DiException::invalidAbstractDependency($abstract); + } + + if (!is_a($instance, $abstract)) { + throw DiException::invalidAbstractDependency($abstract); + } + + if (isset($this->container[$abstract])) { + throw DiException::dependencyAlreadyRegistered($abstract); + } + + if (!$override && isset($this->dependencies[$abstract])) { + throw DiException::dependencyAlreadyRegistered($abstract); + } + + if (!isset($this->dependencies[$abstract])) { + $this->dependencies[$abstract] = get_class($instance); + } + + $this->container[$abstract] = $instance; + } + + /** + * Retrieves a shared instance of the given dependency. + * @template T of object + * @param class-string $dependency + * @param array $args + * @return T + * @throws DiException|ReflectionException + */ + public function get(string $dependency, array $args = []) + { + if (!$this->isRegistered($dependency)) { + if (!$this->instantiable($dependency)) { + throw DiException::dependencyNotRegistered($dependency); + } + $this->register($dependency); + } + + return $this->resolve($dependency, $args, true); + } + + /** + * Creates new instance of the given dependency. + * @template T of object + * @param class-string $dependency + * @param array $args + * @return T + * @throws DiException|ReflectionException + */ + public function create(string $dependency, array $args = []) + { + if (!$this->isRegistered($dependency)) { + $this->register($dependency); + } + + return $this->resolve($dependency, $args, false); + } + + /** + * Autowire callable parameters + * @param array $args + * @return array + * @throws DiException|ReflectionException + */ + public function autowire(callable $entry, array $args = []): array + { + if ($entry instanceof Closure) { + $reflection = new ReflectionFunction($entry); + } elseif (is_array($entry)) { + [$target, $method] = $entry; + $reflection = new ReflectionMethod($target, $method); + } else { + throw DiException::invalidCallable(); + } + + return $this->resolveParameters($reflection->getParameters(), $args); + } + + public function reset(): void + { + $this->dependencies = []; + $this->resetContainer(); + } + + public function resetContainer(): void + { + $this->container = []; + $this->resolving = []; + } + + /** + * Resolves the dependency + * @param array $args + * @return mixed|object + * @throws DiException|ReflectionException + */ + private function resolve(string $abstract, array $args = [], bool $singleton = true) + { + $this->checkCircularDependency($abstract); + $this->resolving[$abstract] = true; + + try { + $concrete = $this->dependencies[$abstract]; + + if ($singleton) { + if (!isset($this->container[$abstract])) { + $this->container[$abstract] = $this->instantiate($concrete, $args); + } + return $this->container[$abstract]; + } + + return $this->instantiate($concrete, $args); + + } finally { + unset($this->resolving[$abstract]); + } + } + + /** + * Instantiates the dependency + * @param class-string $concrete + * @param array $args + * @return mixed + * @throws ReflectionException|DiException + */ + private function instantiate(string $concrete, array $args = []) + { + $class = new ReflectionClass($concrete); + $constructor = $class->getConstructor(); + + $params = $constructor + ? $this->resolveParameters($constructor->getParameters(), $args) + : []; + + return new $concrete(...$params); + } + + /** + * Resolve parameter list + * @param array $parameters + * @param array $args + * @return array + * @throws DiException|ReflectionException + */ + private function resolveParameters(array $parameters, array &$args = []): array + { + $resolved = []; + + foreach ($parameters as $param) { + $resolved[] = $this->resolveParameter($param, $args); + } + + return $resolved; + } + + /** + * Resolve single parameter + * @param array $args + * @return array|mixed|object|null + * @throws DiException|ReflectionException + */ + private function resolveParameter(ReflectionParameter $param, array &$args = []) + { + $type = null; + + if ($param->getType() instanceof \ReflectionNamedType) { + $type = $param->getType()->getName(); + } + + if ($type !== null && isset($this->dependencies[$type])) { + /** @var class-string $type */ + return $this->get($type); + } + + if ($type !== null && $this->instantiable($type)) { + /** @var class-string $type */ + return $this->create($type); + } + + if ($type === 'array') { + return $args; + } + + if ($args !== []) { + return array_shift($args); + } + + return $param->isDefaultValueAvailable() + ? $param->getDefaultValue() + : null; + } + + /** + * Checks if the class is instantiable + */ + private function instantiable(string $class): bool + { + return class_exists($class) + && (new ReflectionClass($class))->isInstantiable(); + } + + /** + * @throws DiException + */ + private function checkCircularDependency(string $abstract): void + { + if (isset($this->resolving[$abstract])) { + $chain = implode(' -> ', array_keys($this->resolving)) . ' -> ' . $abstract; + throw DiException::circularDependency($chain); + } + } +} From 96951cf5e75d3d18a0fe93980fa24ce06492c1ba Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:16:12 +0400 Subject: [PATCH 48/77] [#455] Refactor Di to static facade delegating to DiContainer --- src/Di/Di.php | 239 +++++++------------------------------------------- 1 file changed, 34 insertions(+), 205 deletions(-) diff --git a/src/Di/Di.php b/src/Di/Di.php index cb9f7087..c84c4307 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -18,32 +18,41 @@ use Quantum\Di\Exceptions\DiException; use ReflectionException; -use ReflectionParameter; -use ReflectionFunction; -use ReflectionMethod; -use ReflectionClass; -use Closure; /** * Di Class + * + * Static facade that delegates all calls to the current DiContainer instance. + * Preserves the existing static API for full backward compatibility. + * * @package Quantum/Di */ class Di { /** - * @var array + * @var DiContainer|null */ - private static array $dependencies = []; + private static ?DiContainer $current = null; /** - * @var array + * Sets the current container instance */ - private static array $container = []; + public static function setCurrent(DiContainer $container): void + { + self::$current = $container; + } /** - * @var array + * Gets the current container instance, lazily creating one if needed */ - private static array $resolving = []; + public static function getCurrent(): DiContainer + { + if (self::$current === null) { + self::$current = new DiContainer(); + } + + return self::$current; + } /** * Register dependencies @@ -52,11 +61,7 @@ class Di */ public static function registerDependencies(array $dependencies): void { - foreach ($dependencies as $abstract => $concrete) { - if (!self::isRegistered($abstract)) { - self::register($concrete, $abstract); - } - } + self::getCurrent()->registerDependencies($dependencies); } /** @@ -65,21 +70,7 @@ public static function registerDependencies(array $dependencies): void */ public static function register(string $concrete, ?string $abstract = null): void { - $key = $abstract ?? $concrete; - - if (isset(self::$dependencies[$key])) { - throw DiException::dependencyAlreadyRegistered($key); - } - - if (!class_exists($concrete)) { - throw DiException::dependencyNotInstantiable($concrete); - } - - if ($abstract !== null && !class_exists($abstract) && !interface_exists($abstract)) { - throw DiException::invalidAbstractDependency($abstract); - } - - self::$dependencies[$key] = $concrete; + self::getCurrent()->register($concrete, $abstract); } /** @@ -87,7 +78,7 @@ public static function register(string $concrete, ?string $abstract = null): voi */ public static function isRegistered(string $abstract): bool { - return isset(self::$dependencies[$abstract]); + return self::getCurrent()->isRegistered($abstract); } /** @@ -95,7 +86,7 @@ public static function isRegistered(string $abstract): bool */ public static function has(string $abstract): bool { - return isset(self::$container[$abstract]); + return self::getCurrent()->has($abstract); } /** @@ -107,27 +98,7 @@ public static function has(string $abstract): bool */ public static function set(string $abstract, object $instance, bool $override = true): void { - if (!class_exists($abstract) && !interface_exists($abstract)) { - throw DiException::invalidAbstractDependency($abstract); - } - - if (!is_a($instance, $abstract)) { - throw DiException::invalidAbstractDependency($abstract); - } - - if (isset(self::$container[$abstract])) { - throw DiException::dependencyAlreadyRegistered($abstract); - } - - if (!$override && isset(self::$dependencies[$abstract])) { - throw DiException::dependencyAlreadyRegistered($abstract); - } - - if (!isset(self::$dependencies[$abstract])) { - self::$dependencies[$abstract] = get_class($instance); - } - - self::$container[$abstract] = $instance; + self::getCurrent()->set($abstract, $instance, $override); } /** @@ -140,14 +111,7 @@ public static function set(string $abstract, object $instance, bool $override = */ public static function get(string $dependency, array $args = []) { - if (!self::isRegistered($dependency)) { - if (!self::instantiable($dependency)) { - throw DiException::dependencyNotRegistered($dependency); - } - self::register($dependency); - } - - return self::resolve($dependency, $args, true); + return self::getCurrent()->get($dependency, $args); } /** @@ -160,11 +124,7 @@ public static function get(string $dependency, array $args = []) */ public static function create(string $dependency, array $args = []) { - if (!self::isRegistered($dependency)) { - self::register($dependency); - } - - return self::resolve($dependency, $args, false); + return self::getCurrent()->create($dependency, $args); } /** @@ -175,153 +135,22 @@ public static function create(string $dependency, array $args = []) */ public static function autowire(callable $entry, array $args = []): array { - if ($entry instanceof Closure) { - $reflection = new ReflectionFunction($entry); - } elseif (is_array($entry)) { - [$target, $method] = $entry; - $reflection = new ReflectionMethod($target, $method); - } else { - throw DiException::invalidCallable(); - } - - return self::resolveParameters($reflection->getParameters(), $args); - } - - public static function reset(): void - { - self::$dependencies = []; - self::resetContainer(); - } - - public static function resetContainer(): void - { - self::$container = []; - self::$resolving = []; - } - - /** - * Resolves the dependency - * @param array $args - * @return mixed|object - * @throws DiException|ReflectionException - */ - private static function resolve(string $abstract, array $args = [], bool $singleton = true) - { - self::checkCircularDependency($abstract); - self::$resolving[$abstract] = true; - - try { - $concrete = self::$dependencies[$abstract]; - - if ($singleton) { - if (!isset(self::$container[$abstract])) { - self::$container[$abstract] = self::instantiate($concrete, $args); - } - return self::$container[$abstract]; - } - - return self::instantiate($concrete, $args); - - } finally { - unset(self::$resolving[$abstract]); - } - } - - /** - * Instantiates the dependency - * @param class-string $concrete - * @param array $args - * @return mixed - * @throws ReflectionException|DiException - */ - private static function instantiate(string $concrete, array $args = []) - { - $class = new ReflectionClass($concrete); - $constructor = $class->getConstructor(); - - $params = $constructor - ? self::resolveParameters($constructor->getParameters(), $args) - : []; - - return new $concrete(...$params); - } - - /** - * Resolve parameter list - * @param array $parameters - * @param array $args - * @return array - * @throws DiException|ReflectionException - */ - private static function resolveParameters(array $parameters, array &$args = []): array - { - $resolved = []; - - foreach ($parameters as $param) { - $resolved[] = self::resolveParameter($param, $args); - } - - return $resolved; - } - - /** - * Resolve single parameter - * @param array $args - * @return array|mixed|object|null - * @throws DiException|ReflectionException - */ - private static function resolveParameter(ReflectionParameter $param, array &$args = []) - { - $type = null; - - if ($param->getType() instanceof \ReflectionNamedType) { - $type = $param->getType()->getName(); - } - - // prefer registered dependency - if ($type !== null && isset(self::$dependencies[$type])) { - /** @var class-string $type */ - return self::get($type); - } - - // fallback instantiable class - if ($type !== null && self::instantiable($type)) { - /** @var class-string $type */ - return self::create($type); - } - - // array param receives remaining args - if ($type === 'array') { - return $args; - } - - // positional args fallback - if ($args !== []) { - return array_shift($args); - } - - return $param->isDefaultValueAvailable() - ? $param->getDefaultValue() - : null; + return self::getCurrent()->autowire($entry, $args); } /** - * Checks if the class is instantiable + * Resets the current container by replacing it with a fresh instance */ - protected static function instantiable(string $class): bool + public static function reset(): void { - return class_exists($class) - && (new ReflectionClass($class))->isInstantiable(); + self::$current = new DiContainer(); } /** - * @throws DiException + * Resets only the resolved instances, keeping dependency registrations */ - private static function checkCircularDependency(string $abstract): void + public static function resetContainer(): void { - if (isset(self::$resolving[$abstract])) { - $chain = implode(' -> ', array_keys(self::$resolving)) . ' -> ' . $abstract; - throw DiException::circularDependency($chain); - } + self::getCurrent()->resetContainer(); } } From 3926ca11e4f747cde36e57aa697170d864e85122 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:16:43 +0400 Subject: [PATCH 49/77] [#455] Update AppFactory to create DiContainer per execution --- src/App/Factories/AppFactory.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index 0e6809ee..fc5d68f6 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -21,6 +21,7 @@ use Quantum\App\Exceptions\AppException; use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; use Quantum\App\App; use Quantum\Di\Di; @@ -70,7 +71,8 @@ private static function createInstance(string $type, string $baseDir): App throw AppException::adapterNotSupported($type); } - Di::reset(); + $container = new DiContainer(); + Di::setCurrent($container); $adapterClass = self::ADAPTERS[$type]; From 6bf59571a4f88f662a191aa21302176d56cd9311 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:17:09 +0400 Subject: [PATCH 50/77] [#455] Update DiTest and add DiContainer isolation tests --- tests/Unit/Di/DiTest.php | 105 +++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 15 deletions(-) diff --git a/tests/Unit/Di/DiTest.php b/tests/Unit/Di/DiTest.php index a42ec741..a0b34235 100644 --- a/tests/Unit/Di/DiTest.php +++ b/tests/Unit/Di/DiTest.php @@ -56,6 +56,7 @@ public function __construct(CircularDependencyA $a) use Quantum\Di\Exceptions\DiException; use Quantum\Tests\Unit\AppTestCase; use Quantum\Service\DummyService; + use Quantum\Di\DiContainer; use Quantum\Loader\Loader; use Quantum\Http\Response; use Quantum\Http\Request; @@ -358,15 +359,8 @@ public function testDiReset(): void Di::reset(); - $dependenciesProperty = new ReflectionProperty(Di::class, 'dependencies'); - $dependenciesProperty->setAccessible(true); - - $containerProperty = new ReflectionProperty(Di::class, 'container'); - $containerProperty->setAccessible(true); - - $this->assertEmpty($dependenciesProperty->getValue()); - - $this->assertEmpty($containerProperty->getValue()); + $this->assertFalse(Di::isRegistered(DummyService::class)); + $this->assertFalse(Di::has(DummyService::class)); } public function testDiResetContainer(): void @@ -376,18 +370,99 @@ public function testDiResetContainer(): void Di::get(DummyService::class); $this->assertTrue(Di::isRegistered(DummyService::class)); + $this->assertTrue(Di::has(DummyService::class)); Di::resetContainer(); - $dependenciesProperty = new ReflectionProperty(Di::class, 'dependencies'); - $dependenciesProperty->setAccessible(true); + $this->assertTrue(Di::isRegistered(DummyService::class)); + $this->assertFalse(Di::has(DummyService::class)); + } + + public function testDiSetAndGetCurrent(): void + { + $container = new DiContainer(); + Di::setCurrent($container); + + $this->assertSame($container, Di::getCurrent()); + } + + public function testDiGetCurrentCreatesDefaultContainer(): void + { + $currentProp = new ReflectionProperty(Di::class, 'current'); + $currentProp->setAccessible(true); + $currentProp->setValue(null, null); + + $container = Di::getCurrent(); + + $this->assertInstanceOf(DiContainer::class, $container); + } + + public function testDiResetCreatesNewContainer(): void + { + $containerBefore = Di::getCurrent(); + + Di::reset(); + + $containerAfter = Di::getCurrent(); + + $this->assertNotSame($containerBefore, $containerAfter); + } + + public function testDiContainerIsolation(): void + { + $container1 = new DiContainer(); + $container1->register(DummyService::class); + $container1->get(DummyService::class); + + $container2 = new DiContainer(); + + $this->assertTrue($container1->isRegistered(DummyService::class)); + $this->assertTrue($container1->has(DummyService::class)); + + $this->assertFalse($container2->isRegistered(DummyService::class)); + $this->assertFalse($container2->has(DummyService::class)); + } + + public function testDiContainerInstanceMethods(): void + { + $container = new DiContainer(); + + $container->register(DummyService::class); + + $this->assertTrue($container->isRegistered(DummyService::class)); + $this->assertFalse($container->has(DummyService::class)); + + $instance = $container->get(DummyService::class); + + $this->assertInstanceOf(DummyService::class, $instance); + $this->assertTrue($container->has(DummyService::class)); + $this->assertSame($instance, $container->get(DummyService::class)); + } + + public function testDiContainerResetClearsAll(): void + { + $container = new DiContainer(); + + $container->register(DummyService::class); + $container->get(DummyService::class); + + $container->reset(); + + $this->assertFalse($container->isRegistered(DummyService::class)); + $this->assertFalse($container->has(DummyService::class)); + } + + public function testDiContainerResetContainerKeepsDependencies(): void + { + $container = new DiContainer(); - $containerProperty = new ReflectionProperty(Di::class, 'container'); - $containerProperty->setAccessible(true); + $container->register(DummyService::class); + $container->get(DummyService::class); - $this->assertNotEmpty($dependenciesProperty->getValue()); + $container->resetContainer(); - $this->assertEmpty($containerProperty->getValue()); + $this->assertTrue($container->isRegistered(DummyService::class)); + $this->assertFalse($container->has(DummyService::class)); } } } From 173009b1d9d0f790333038e4633d6b1a4fe82604 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:58:02 +0400 Subject: [PATCH 51/77] [#455] Split DiTest into facade and container tests, fix PHPStan throws Made-with: Cursor --- src/Di/Di.php | 105 ++------- tests/Unit/Di/DiContainerTest.php | 319 ++++++++++++++++++++++++++ tests/Unit/Di/DiTest.php | 368 ++++-------------------------- 3 files changed, 379 insertions(+), 413 deletions(-) create mode 100644 tests/Unit/Di/DiContainerTest.php diff --git a/src/Di/Di.php b/src/Di/Di.php index c84c4307..24cde4bd 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -26,6 +26,15 @@ * Preserves the existing static API for full backward compatibility. * * @package Quantum/Di + * @method static void registerDependencies(array $dependencies) + * @method static void register(string $concrete, ?string $abstract = null) + * @method static bool isRegistered(string $abstract) + * @method static bool has(string $abstract) + * @method static void set(string $abstract, object $instance, bool $override = true) + * @method static mixed get(string $dependency, array $args = []) + * @method static mixed create(string $dependency, array $args = []) + * @method static array autowire(callable $entry, array $args = []) + * @method static void resetContainer() */ class Di { @@ -54,90 +63,6 @@ public static function getCurrent(): DiContainer return self::$current; } - /** - * Register dependencies - * @param array $dependencies - * @throws DiException - */ - public static function registerDependencies(array $dependencies): void - { - self::getCurrent()->registerDependencies($dependencies); - } - - /** - * Registers new dependency - * @throws DiException - */ - public static function register(string $concrete, ?string $abstract = null): void - { - self::getCurrent()->register($concrete, $abstract); - } - - /** - * Checks if a dependency registered - */ - public static function isRegistered(string $abstract): bool - { - return self::getCurrent()->isRegistered($abstract); - } - - /** - * Checks if an instance exists in the container - */ - public static function has(string $abstract): bool - { - return self::getCurrent()->has($abstract); - } - - /** - * Sets an instance into container - * @template T of object - * @param class-string $abstract - * @param T $instance - * @throws DiException - */ - public static function set(string $abstract, object $instance, bool $override = true): void - { - self::getCurrent()->set($abstract, $instance, $override); - } - - /** - * Retrieves a shared instance of the given dependency. - * @template T of object - * @param class-string $dependency - * @param array $args - * @return T - * @throws DiException|ReflectionException - */ - public static function get(string $dependency, array $args = []) - { - return self::getCurrent()->get($dependency, $args); - } - - /** - * Creates new instance of the given dependency. - * @template T of object - * @param class-string $dependency - * @param array $args - * @return T - * @throws DiException|ReflectionException - */ - public static function create(string $dependency, array $args = []) - { - return self::getCurrent()->create($dependency, $args); - } - - /** - * Autowire callable parameters - * @param array $args - * @return array - * @throws DiException|ReflectionException - */ - public static function autowire(callable $entry, array $args = []): array - { - return self::getCurrent()->autowire($entry, $args); - } - /** * Resets the current container by replacing it with a fresh instance */ @@ -147,10 +72,16 @@ public static function reset(): void } /** - * Resets only the resolved instances, keeping dependency registrations + * @param array $arguments + * @return mixed + * @throws DiException|ReflectionException */ - public static function resetContainer(): void + public static function __callStatic(string $method, array $arguments) { - self::getCurrent()->resetContainer(); + if (!method_exists(self::getCurrent(), $method)) { + throw DiException::invalidCallable($method); + } + + return self::getCurrent()->$method(...$arguments); } } diff --git a/tests/Unit/Di/DiContainerTest.php b/tests/Unit/Di/DiContainerTest.php new file mode 100644 index 00000000..57d5ae80 --- /dev/null +++ b/tests/Unit/Di/DiContainerTest.php @@ -0,0 +1,319 @@ +container = new DiContainer(); + } + + public function testRegisterDependency(): void + { + $this->container->register(Setup::class); + + $this->assertInstanceOf(Setup::class, $this->container->get(Setup::class)); + } + + public function testAttemptingToRegisterAlreadyRegisteredDependency(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `Quantum\Loader\Setup` is already registered.'); + + $this->container->register(Setup::class); + $this->container->register(Setup::class); + } + + public function testAttemptingToRegisterNonExistentClass(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `NonExistentClass` is not instantiable.'); + + $this->container->register('NonExistentClass'); + } + + public function testAttemptingToRegisterNonExistentAbstract(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `NonExistentInterface` is not valid abstract class.'); + + $this->container->register(DummyService::class, 'NonExistentInterface'); + } + + public function testIsRegistered(): void + { + $this->assertFalse($this->container->isRegistered(DummyService::class)); + + $this->container->register(DummyService::class); + + $this->assertTrue($this->container->isRegistered(DummyService::class)); + } + + public function testHasReturnsFalseBeforeResolve(): void + { + $this->container->register(DummyService::class); + + $this->assertFalse($this->container->has(DummyService::class)); + } + + public function testHasReturnsTrueAfterGet(): void + { + $this->container->register(DummyService::class); + + $this->container->get(DummyService::class); + + $this->assertTrue($this->container->has(DummyService::class)); + } + + public function testHasReturnsTrueAfterSet(): void + { + $instance = new DummyService(); + + $this->container->set(DummyServiceInterface::class, $instance); + + $this->assertTrue($this->container->has(DummyServiceInterface::class)); + } + + public function testHasReturnsFalseAfterResetContainer(): void + { + $this->container->register(DummyService::class); + $this->container->get(DummyService::class); + + $this->assertTrue($this->container->has(DummyService::class)); + + $this->container->resetContainer(); + + $this->assertFalse($this->container->has(DummyService::class)); + } + + public function testHasReturnsFalseAfterReset(): void + { + $this->container->register(DummyService::class); + $this->container->get(DummyService::class); + + $this->assertTrue($this->container->has(DummyService::class)); + + $this->container->reset(); + + $this->assertFalse($this->container->has(DummyService::class)); + } + + public function testAbstractToConcreteBinding(): void + { + $this->container->register(DummyService::class, DummyServiceInterface::class); + + $instance = $this->container->get(DummyServiceInterface::class); + + $this->assertInstanceOf(DummyService::class, $instance); + } + + public function testSetBindsInstanceToAbstract(): void + { + $instance = new DummyService(); + + $this->container->set(DummyServiceInterface::class, $instance); + + $resolved = $this->container->get(DummyServiceInterface::class); + + $this->assertSame($instance, $resolved); + $this->assertInstanceOf(DummyService::class, $resolved); + } + + public function testSetWorksWithoutPriorRegister(): void + { + $instance = new DummyService(); + + $this->assertFalse($this->container->isRegistered(DummyServiceInterface::class)); + + $this->container->set(DummyServiceInterface::class, $instance); + + $this->assertTrue($this->container->isRegistered(DummyServiceInterface::class)); + $this->assertSame($instance, $this->container->get(DummyServiceInterface::class)); + } + + public function testSetRejectsWrongInstanceType(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage( + 'The dependency `' . DummyServiceInterface::class . '` is not valid abstract class.' + ); + + $this->container->set(DummyServiceInterface::class, new \stdClass()); + } + + public function testSetRejectsInvalidAbstract(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage( + 'The dependency `NonExistentInterface` is not valid abstract class.' + ); + + $this->container->set('NonExistentInterface', new DummyService()); + } + + public function testSetRejectsWhenAlreadyResolved(): void + { + $this->container->register(DummyService::class); + $this->container->get(DummyService::class); + + $this->expectException(DiException::class); + $this->expectExceptionMessage( + 'The dependency `' . DummyService::class . '` is already registered.' + ); + + $this->container->set(DummyService::class, new DummyService()); + } + + public function testSetOverridesRegisteredButNotResolved(): void + { + $this->container->register(DummyService::class, DummyServiceInterface::class); + + $instance = new DummyService(); + $this->container->set(DummyServiceInterface::class, $instance); + + $resolved = $this->container->get(DummyServiceInterface::class); + + $this->assertSame($instance, $resolved); + } + + public function testGetAutoRegistersInstantiableClass(): void + { + $this->assertFalse($this->container->isRegistered(DiException::class)); + + $instance = $this->container->get(DiException::class); + + $this->assertInstanceOf(DiException::class, $instance); + $this->assertTrue($this->container->isRegistered(DiException::class)); + } + + public function testGetThrowsForNonInstantiableClass(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `Quantum\Service\DummyServiceInterface` is not registered.'); + + $this->container->get(DummyServiceInterface::class); + } + + public function testCircularDependencyDetectedAtResolve(): void + { + $this->expectException(DiException::class); + $this->expectExceptionMessage( + 'Circular dependency detected: `' . CircularDependencyA::class . + ' -> ' . CircularDependencyB::class . + ' -> ' . CircularDependencyA::class . '`' + ); + + $this->container->register(CircularDependencyA::class); + $this->container->register(CircularDependencyB::class); + + $this->container->create(CircularDependencyA::class); + } + + public function testGetReturnsSingleton(): void + { + $this->container->register(DummyService::class); + + $instance1 = $this->container->get(DummyService::class); + $instance2 = $this->container->get(DummyService::class); + + $this->assertInstanceOf(DummyService::class, $instance1); + $this->assertSame($instance1, $instance2); + } + + public function testCreateReturnsNewInstance(): void + { + $this->container->register(DummyService::class); + + $instance1 = $this->container->create(DummyService::class); + $instance2 = $this->container->create(DummyService::class); + + $this->assertInstanceOf(DummyService::class, $instance1); + $this->assertNotSame($instance1, $instance2); + } + + public function testAutowire(): void + { + $this->container->register(Request::class); + $this->container->register(Response::class); + + $params = $this->container->autowire([new TestDiController(), 'index']); + + $this->assertInstanceOf(Request::class, $params[0]); + $this->assertInstanceOf(Response::class, $params[1]); + $this->assertInstanceOf(ViewFactory::class, $params[2]); + + $callback = function (Request $request, Response $response): void { + }; + + $params = $this->container->autowire($callback); + + $this->assertInstanceOf(Request::class, $params[0]); + $this->assertInstanceOf(Response::class, $params[1]); + } + + public function testAutowireWithAbstract(): void + { + $this->container->register(DummyService::class, DummyServiceInterface::class); + + $controller = new TestDiController(); + + $params = $this->container->autowire([$controller, 'handleService']); + + $this->assertInstanceOf(DummyServiceInterface::class, $params[0]); + $this->assertInstanceOf(DummyService::class, $params[0]); + } + + public function testResetClearsAll(): void + { + $this->container->register(DummyService::class); + $this->container->get(DummyService::class); + + $this->container->reset(); + + $this->assertFalse($this->container->isRegistered(DummyService::class)); + $this->assertFalse($this->container->has(DummyService::class)); + } + + public function testResetContainerKeepsDependencies(): void + { + $this->container->register(DummyService::class); + $this->container->get(DummyService::class); + + $this->container->resetContainer(); + + $this->assertTrue($this->container->isRegistered(DummyService::class)); + $this->assertFalse($this->container->has(DummyService::class)); + } + + public function testContainerIsolation(): void + { + $container1 = new DiContainer(); + $container1->register(DummyService::class); + $container1->get(DummyService::class); + + $container2 = new DiContainer(); + + $this->assertTrue($container1->isRegistered(DummyService::class)); + $this->assertTrue($container1->has(DummyService::class)); + + $this->assertFalse($container2->isRegistered(DummyService::class)); + $this->assertFalse($container2->has(DummyService::class)); + } +} diff --git a/tests/Unit/Di/DiTest.php b/tests/Unit/Di/DiTest.php index a0b34235..a0037153 100644 --- a/tests/Unit/Di/DiTest.php +++ b/tests/Unit/Di/DiTest.php @@ -48,16 +48,10 @@ public function __construct(CircularDependencyA $a) namespace Quantum\Tests\Unit\Di { - use Quantum\Service\DummyServiceInterface; - use Quantum\Controllers\TestDiController; - use Quantum\Service\CircularDependencyA; - use Quantum\Service\CircularDependencyB; - use Quantum\View\Factories\ViewFactory; use Quantum\Di\Exceptions\DiException; use Quantum\Tests\Unit\AppTestCase; use Quantum\Service\DummyService; use Quantum\Di\DiContainer; - use Quantum\Loader\Loader; use Quantum\Http\Response; use Quantum\Http\Request; use Quantum\Loader\Setup; @@ -71,398 +65,120 @@ public function setUp(): void parent::setUp(); } - public function testDiRegisterDependency(): void - { - Di::register(Setup::class); - - $this->assertInstanceOf(Setup::class, Di::get(Setup::class)); - } - - public function testDiAttemptingToRegisterAlreadyRegisteredDependency(): void + public function testFacadeDelegatesToCurrentContainer(): void { - $this->expectException(DiException::class); + $container = new DiContainer(); + Di::setCurrent($container); - $this->expectExceptionMessage('The dependency `Quantum\Loader\Setup` is already registered.'); + Di::register(DummyService::class); - Di::register(Setup::class); - Di::register(Setup::class); - } + $this->assertTrue($container->isRegistered(DummyService::class)); - public function testDiAttemptingToRegisterNonExistentClass(): void - { - $this->expectException(DiException::class); - $this->expectExceptionMessage('The dependency `NonExistentClass` is not instantiable.'); + $instance = Di::get(DummyService::class); - Di::register('NonExistentClass'); + $this->assertInstanceOf(DummyService::class, $instance); + $this->assertSame($instance, $container->get(DummyService::class)); } - public function testDiAttemptingToRegisterNonExistentAbstract(): void + public function testSetAndGetCurrent(): void { - $this->expectException(DiException::class); - $this->expectExceptionMessage('The dependency `NonExistentInterface` is not valid abstract class.'); + $container = new DiContainer(); + Di::setCurrent($container); - Di::register(DummyService::class, 'NonExistentInterface'); + $this->assertSame($container, Di::getCurrent()); } - public function testDiIsRegistered(): void + public function testGetCurrentCreatesDefaultContainer(): void { - $this->assertFalse(Di::isRegistered(DummyService::class)); - - Di::register(DummyService::class); - - $this->assertTrue(Di::isRegistered(DummyService::class)); - } + $currentProp = new ReflectionProperty(Di::class, 'current'); + $currentProp->setAccessible(true); + $currentProp->setValue(null, null); - public function testDiHasReturnsFalseBeforeResolve(): void - { - Di::register(DummyService::class); + $container = Di::getCurrent(); - $this->assertFalse(Di::has(DummyService::class)); + $this->assertInstanceOf(DiContainer::class, $container); } - public function testDiHasReturnsTrueAfterGet(): void + public function testResetCreatesNewContainer(): void { - Di::register(DummyService::class); - - Di::get(DummyService::class); - - $this->assertTrue(Di::has(DummyService::class)); - } + $containerBefore = Di::getCurrent(); - public function testDiHasReturnsTrueAfterSet(): void - { - $instance = new DummyService(); + Di::reset(); - Di::set(DummyServiceInterface::class, $instance); + $containerAfter = Di::getCurrent(); - $this->assertTrue(Di::has(DummyServiceInterface::class)); + $this->assertNotSame($containerBefore, $containerAfter); } - public function testDiHasReturnsFalseAfterResetContainer(): void + public function testResetClearsRegistrationsAndInstances(): void { Di::register(DummyService::class); - Di::get(DummyService::class); + $this->assertTrue(Di::isRegistered(DummyService::class)); $this->assertTrue(Di::has(DummyService::class)); - Di::resetContainer(); + Di::reset(); + $this->assertFalse(Di::isRegistered(DummyService::class)); $this->assertFalse(Di::has(DummyService::class)); } - public function testDiHasReturnsFalseAfterReset(): void + public function testResetContainerKeepsRegistrations(): void { Di::register(DummyService::class); - Di::get(DummyService::class); + $this->assertTrue(Di::isRegistered(DummyService::class)); $this->assertTrue(Di::has(DummyService::class)); - Di::reset(); + Di::resetContainer(); + $this->assertTrue(Di::isRegistered(DummyService::class)); $this->assertFalse(Di::has(DummyService::class)); } - public function testDiAbstractToConcreteBinding(): void - { - Di::register(DummyService::class, DummyServiceInterface::class); - - $instance = Di::get(DummyServiceInterface::class); - - $this->assertInstanceOf(DummyService::class, $instance); - } - - public function testDiSetBindsInstanceToAbstract(): void - { - $instance = new DummyService(); - - Di::set(DummyServiceInterface::class, $instance); - - $resolved = Di::get(DummyServiceInterface::class); - - $this->assertSame($instance, $resolved); - $this->assertInstanceOf(DummyService::class, $resolved); - } - - public function testDiSetWorksWithoutPriorRegister(): void - { - $instance = new DummyService(); - - $this->assertFalse(Di::isRegistered(DummyServiceInterface::class)); - - Di::set(DummyServiceInterface::class, $instance); - - $this->assertTrue(Di::isRegistered(DummyServiceInterface::class)); - - $this->assertSame($instance, Di::get(DummyServiceInterface::class)); - } - - public function testDiSetRejectsWrongInstanceType(): void - { - $this->expectException(DiException::class); - $this->expectExceptionMessage( - 'The dependency `' . DummyServiceInterface::class . '` is not valid abstract class.' - ); - - Di::set(DummyServiceInterface::class, new \stdClass()); - } - - public function testDiSetRejectsInvalidAbstract(): void - { - $this->expectException(DiException::class); - $this->expectExceptionMessage( - 'The dependency `NonExistentInterface` is not valid abstract class.' - ); - - Di::set('NonExistentInterface', new DummyService()); - } - - public function testDiSetRejectsWhenAlreadyResolved(): void + public function testFacadeRegisterAndGet(): void { - Di::register(DummyService::class); - - Di::get(DummyService::class); - - $this->expectException(DiException::class); - $this->expectExceptionMessage( - 'The dependency `' . DummyService::class . '` is already registered.' - ); + Di::register(Setup::class); - Di::set(DummyService::class, new DummyService()); + $this->assertInstanceOf(Setup::class, Di::get(Setup::class)); } - public function testDiSetOverridesRegisteredButNotResolved(): void + public function testFacadeSet(): void { - Di::register(DummyService::class, DummyServiceInterface::class); - $instance = new DummyService(); - Di::set(DummyServiceInterface::class, $instance); - - $resolved = Di::get(DummyServiceInterface::class); + Di::set(DummyService::class, $instance); - $this->assertSame($instance, $resolved); + $this->assertSame($instance, Di::get(DummyService::class)); } - public function testDiGetCoreDependencies(): void + public function testFacadeCreate(): void { - $this->assertInstanceOf(Loader::class, Di::get(Loader::class)); - - $this->assertInstanceOf(Request::class, Di::get(Request::class)); - - $this->assertInstanceOf(Response::class, Di::get(Response::class)); - } - - public function testDiGetAutoRegistersInstantiableClass(): void - { - $this->assertFalse(Di::isRegistered(DiException::class)); - - $instance = Di::get(DiException::class); - - $this->assertInstanceOf(DiException::class, $instance); - $this->assertTrue(Di::isRegistered(DiException::class)); - } - - public function testDiGetThrowsForNonInstantiableClass(): void - { - $this->expectException(DiException::class); - - $this->expectExceptionMessage('The dependency `Quantum\Service\DummyServiceInterface` is not registered.'); - - Di::get(DummyServiceInterface::class); - } - - public function testDiCircularDependencyDetectedAtResolve(): void - { - $this->expectException(DiException::class); - - $this->expectExceptionMessage( - 'Circular dependency detected: `' . CircularDependencyA::class . - ' -> ' . CircularDependencyB::class . - ' -> ' . CircularDependencyA::class . '`' - ); - - Di::register(CircularDependencyA::class); - Di::register(CircularDependencyB::class); - - Di::create(CircularDependencyA::class); - } - - public function testDiGetReturnsSingleton(): void - { - Di::register(DummyService::class); - - $instance1 = Di::get(DummyService::class); - - $instance2 = Di::get(DummyService::class); - - $this->assertInstanceOf(DummyService::class, $instance1); - - $this->assertSame($instance1, $instance2); - } - - public function testDiCreateReturnsNewInstance(): void - { - Di::register(DummyService::class); - $instance1 = Di::create(DummyService::class); - $instance2 = Di::create(DummyService::class); $this->assertInstanceOf(DummyService::class, $instance1); - $this->assertNotSame($instance1, $instance2); } - public function testDiAutowire(): void + public function testFacadeAutowire(): void { - $params = Di::autowire([new TestDiController(), 'index']); - - $this->assertInstanceOf(Request::class, $params[0]); - - $this->assertInstanceOf(Response::class, $params[1]); - - $this->assertInstanceOf(ViewFactory::class, $params[2]); - $callback = function (Request $request, Response $response): void { - // function body }; $params = Di::autowire($callback); $this->assertInstanceOf(Request::class, $params[0]); - $this->assertInstanceOf(Response::class, $params[1]); } - public function testDiAutowireWithAbstract(): void - { - Di::register(DummyService::class, DummyServiceInterface::class); - - $controller = new TestDiController(); - - $params = Di::autowire([$controller, 'handleService']); - - $this->assertInstanceOf(DummyServiceInterface::class, $params[0]); - - $this->assertInstanceOf(DummyService::class, $params[0]); - } - - public function testDiReset(): void - { - Di::register(DummyService::class); - - Di::get(DummyService::class); - - $this->assertTrue(Di::isRegistered(DummyService::class)); - - Di::reset(); - - $this->assertFalse(Di::isRegistered(DummyService::class)); - $this->assertFalse(Di::has(DummyService::class)); - } - - public function testDiResetContainer(): void - { - Di::register(DummyService::class); - - Di::get(DummyService::class); - - $this->assertTrue(Di::isRegistered(DummyService::class)); - $this->assertTrue(Di::has(DummyService::class)); - - Di::resetContainer(); - - $this->assertTrue(Di::isRegistered(DummyService::class)); - $this->assertFalse(Di::has(DummyService::class)); - } - - public function testDiSetAndGetCurrent(): void - { - $container = new DiContainer(); - Di::setCurrent($container); - - $this->assertSame($container, Di::getCurrent()); - } - - public function testDiGetCurrentCreatesDefaultContainer(): void - { - $currentProp = new ReflectionProperty(Di::class, 'current'); - $currentProp->setAccessible(true); - $currentProp->setValue(null, null); - - $container = Di::getCurrent(); - - $this->assertInstanceOf(DiContainer::class, $container); - } - - public function testDiResetCreatesNewContainer(): void - { - $containerBefore = Di::getCurrent(); - - Di::reset(); - - $containerAfter = Di::getCurrent(); - - $this->assertNotSame($containerBefore, $containerAfter); - } - - public function testDiContainerIsolation(): void + public function testCallStaticThrowsForInvalidMethod(): void { - $container1 = new DiContainer(); - $container1->register(DummyService::class); - $container1->get(DummyService::class); - - $container2 = new DiContainer(); - - $this->assertTrue($container1->isRegistered(DummyService::class)); - $this->assertTrue($container1->has(DummyService::class)); - - $this->assertFalse($container2->isRegistered(DummyService::class)); - $this->assertFalse($container2->has(DummyService::class)); - } - - public function testDiContainerInstanceMethods(): void - { - $container = new DiContainer(); - - $container->register(DummyService::class); - - $this->assertTrue($container->isRegistered(DummyService::class)); - $this->assertFalse($container->has(DummyService::class)); - - $instance = $container->get(DummyService::class); - - $this->assertInstanceOf(DummyService::class, $instance); - $this->assertTrue($container->has(DummyService::class)); - $this->assertSame($instance, $container->get(DummyService::class)); - } - - public function testDiContainerResetClearsAll(): void - { - $container = new DiContainer(); - - $container->register(DummyService::class); - $container->get(DummyService::class); - - $container->reset(); - - $this->assertFalse($container->isRegistered(DummyService::class)); - $this->assertFalse($container->has(DummyService::class)); - } - - public function testDiContainerResetContainerKeepsDependencies(): void - { - $container = new DiContainer(); - - $container->register(DummyService::class); - $container->get(DummyService::class); - - $container->resetContainer(); + $this->expectException(DiException::class); - $this->assertTrue($container->isRegistered(DummyService::class)); - $this->assertFalse($container->has(DummyService::class)); + Di::nonExistentMethod(); } } } From 75f6aa4c9c06b688faa813b5aa6a24c9cd24ea4c Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:55:06 +0400 Subject: [PATCH 52/77] [#456] Expand AppContext with baseDir, DiContainer, and typed accessors Made-with: Cursor --- src/App/AppContext.php | 73 ++++++++++++++++++++++++++++++- tests/Unit/App/AppContextTest.php | 30 +++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/App/AppContext.php b/src/App/AppContext.php index ec513d39..1bb68509 100644 --- a/src/App/AppContext.php +++ b/src/App/AppContext.php @@ -16,8 +16,16 @@ namespace Quantum\App; +use Quantum\Di\Exceptions\DiException; +use Quantum\Environment\Environment; use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; +use Quantum\Config\Config; +use Quantum\Http\Response; +use Quantum\Http\Request; use InvalidArgumentException; +use ReflectionException; +use RuntimeException; /** * Class AppContext @@ -27,13 +35,19 @@ class AppContext { private string $mode; - public function __construct(string $mode) + private string $baseDir; + + private ?DiContainer $container; + + public function __construct(string $mode, string $baseDir = '', ?DiContainer $container = null) { if (!in_array($mode, [AppType::WEB, AppType::CONSOLE], true)) { throw new InvalidArgumentException("Invalid app mode: $mode"); } $this->mode = $mode; + $this->baseDir = $baseDir; + $this->container = $container; } public function getMode(): string @@ -41,6 +55,16 @@ public function getMode(): string return $this->mode; } + public function getBaseDir(): string + { + return $this->baseDir; + } + + public function getContainer(): ?DiContainer + { + return $this->container; + } + public function isWebMode(): bool { return $this->mode === AppType::WEB; @@ -50,4 +74,51 @@ public function isConsoleMode(): bool { return $this->mode === AppType::CONSOLE; } + + /** + * @throws DiException|ReflectionException + */ + public function getEnvironment(): Environment + { + return $this->resolveFromContainer(Environment::class); + } + + /** + * @throws DiException|ReflectionException + */ + public function getConfig(): Config + { + return $this->resolveFromContainer(Config::class); + } + + /** + * @throws DiException|ReflectionException + */ + public function getRequest(): Request + { + return $this->resolveFromContainer(Request::class); + } + + /** + * @throws DiException|ReflectionException + */ + public function getResponse(): Response + { + return $this->resolveFromContainer(Response::class); + } + + /** + * @template T of object + * @param class-string $class + * @return T + * @throws DiException|ReflectionException + */ + private function resolveFromContainer(string $class) + { + if ($this->container === null) { + throw new RuntimeException('DiContainer is not set on AppContext.'); + } + + return $this->container->get($class); + } } diff --git a/tests/Unit/App/AppContextTest.php b/tests/Unit/App/AppContextTest.php index 0ec7bece..1b7bff35 100644 --- a/tests/Unit/App/AppContextTest.php +++ b/tests/Unit/App/AppContextTest.php @@ -3,6 +3,7 @@ namespace Quantum\Tests\Unit\App; use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; use Quantum\App\AppContext; use PHPUnit\Framework\TestCase; use InvalidArgumentException; @@ -34,4 +35,33 @@ public function testAppContextRejectsInvalidMode(): void new AppContext('invalid'); } + + public function testAppContextBaseDir(): void + { + $context = new AppContext(AppType::WEB, '/my/base/dir'); + + $this->assertSame('/my/base/dir', $context->getBaseDir()); + } + + public function testAppContextBaseDirDefaultsToEmpty(): void + { + $context = new AppContext(AppType::WEB); + + $this->assertSame('', $context->getBaseDir()); + } + + public function testAppContextContainer(): void + { + $container = new DiContainer(); + $context = new AppContext(AppType::WEB, '/tmp', $container); + + $this->assertSame($container, $context->getContainer()); + } + + public function testAppContextContainerDefaultsToNull(): void + { + $context = new AppContext(AppType::WEB); + + $this->assertNull($context->getContainer()); + } } From 869cc14b1d42c9bb824524874aa288bac3879840 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:05:11 +0400 Subject: [PATCH 53/77] [#456] Wire AppContext through AppFactory and adapters Made-with: Cursor --- src/App/Adapters/AppAdapter.php | 9 ++------ src/App/Adapters/ConsoleAppAdapter.php | 6 ++--- src/App/Adapters/WebAppAdapter.php | 6 ++--- src/App/Factories/AppFactory.php | 5 +++- .../App/Adapters/ConsoleAppAdapterTest.php | 17 ++++++++++++-- tests/Unit/App/Adapters/WebAppAdapterTest.php | 13 ++++++++++- tests/Unit/App/AppTest.php | 23 +++++++++++++++---- 7 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/App/Adapters/AppAdapter.php b/src/App/Adapters/AppAdapter.php index 446ff7e0..41844b5f 100644 --- a/src/App/Adapters/AppAdapter.php +++ b/src/App/Adapters/AppAdapter.php @@ -17,7 +17,6 @@ namespace Quantum\App\Adapters; use Quantum\App\Contracts\AppInterface; -use Quantum\App\Traits\AppTrait; use Quantum\App\AppContext; /** @@ -26,14 +25,10 @@ */ abstract class AppAdapter implements AppInterface { - use AppTrait; - - private static string $baseDir; - protected AppContext $context; - public function __construct(string $mode) + public function __construct(AppContext $context) { - $this->context = new AppContext($mode); + $this->context = $context; } } diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index 48bf89af..ca9005ca 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -26,8 +26,8 @@ use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Traits\ConsoleAppTrait; -use Quantum\App\Enums\AppType; use Quantum\App\BootPipeline; +use Quantum\App\AppContext; use Exception; if (!defined('DS')) { @@ -48,9 +48,9 @@ class ConsoleAppAdapter extends AppAdapter protected Application $application; - public function __construct() + public function __construct(AppContext $context) { - parent::__construct(AppType::CONSOLE); + parent::__construct($context); $this->input = new ArgvInput(); $this->output = new ConsoleOutput(); diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 45a680db..23bd9d39 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -40,8 +40,8 @@ use Quantum\Module\ModuleLoader; use Quantum\Router\RouteFinder; use Quantum\Debugger\Debugger; -use Quantum\App\Enums\AppType; use Quantum\App\BootPipeline; +use Quantum\App\AppContext; use ReflectionException; use Quantum\Di\Di; @@ -53,9 +53,9 @@ class WebAppAdapter extends AppAdapter { use WebAppTrait; - public function __construct() + public function __construct(AppContext $context) { - parent::__construct(AppType::WEB); + parent::__construct($context); $pipeline = new BootPipeline([ new LoadHelpersStage(), diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index fc5d68f6..26739cb3 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -21,6 +21,7 @@ use Quantum\App\Exceptions\AppException; use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Enums\AppType; +use Quantum\App\AppContext; use Quantum\Di\DiContainer; use Quantum\App\App; use Quantum\Di\Di; @@ -74,10 +75,12 @@ private static function createInstance(string $type, string $baseDir): App $container = new DiContainer(); Di::setCurrent($container); + $context = new AppContext($type, $baseDir, $container); + $adapterClass = self::ADAPTERS[$type]; App::setBaseDir($baseDir); - return new App(new $adapterClass()); + return new App(new $adapterClass($context)); } } diff --git a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php index 70257ff2..aa1e18fd 100644 --- a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php +++ b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php @@ -5,7 +5,11 @@ use Quantum\App\Adapters\ConsoleAppAdapter; use Symfony\Component\Console\Application; use PHPUnit\Framework\TestCase; +use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; +use Quantum\App\AppContext; use Quantum\App\App; +use Quantum\Di\Di; use Exception; use Mockery; @@ -13,6 +17,14 @@ class ConsoleAppAdapterTest extends TestCase { private $consoleAppAdapter; + private function createContext(): AppContext + { + $container = new DiContainer(); + Di::setCurrent($container); + + return new AppContext(AppType::CONSOLE, PROJECT_ROOT, $container); + } + public function setUp(): void { App::setBaseDir(PROJECT_ROOT); @@ -33,13 +45,14 @@ public function setUp(): void public function tearDown(): void { config()->flush(); + Di::reset(); } public function testConsoleAppAdapterStartSuccessfully(): void { $_SERVER['argv'] = ['qt', 'list', '--quiet']; - $this->consoleAppAdapter->__construct(); + $this->consoleAppAdapter->__construct($this->createContext()); $result = $this->consoleAppAdapter->start(); @@ -50,7 +63,7 @@ public function testConsoleAppAdapterStartFails(): void { $_SERVER['argv'] = ['qt', 'unknown', '--quiet']; - $this->consoleAppAdapter->__construct(); + $this->consoleAppAdapter->__construct($this->createContext()); $this->expectException(Exception::class); diff --git a/tests/Unit/App/Adapters/WebAppAdapterTest.php b/tests/Unit/App/Adapters/WebAppAdapterTest.php index 20a4431a..45b96e09 100644 --- a/tests/Unit/App/Adapters/WebAppAdapterTest.php +++ b/tests/Unit/App/Adapters/WebAppAdapterTest.php @@ -4,6 +4,9 @@ use Quantum\App\Adapters\WebAppAdapter; use PHPUnit\Framework\TestCase; +use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; +use Quantum\App\AppContext; use Quantum\App\App; use Quantum\Di\Di; @@ -11,11 +14,19 @@ class WebAppAdapterTest extends TestCase { private WebAppAdapter $webAppAdapter; + private function createContext(string $mode = AppType::WEB): AppContext + { + $container = new DiContainer(); + Di::setCurrent($container); + + return new AppContext($mode, PROJECT_ROOT, $container); + } + public function setUp(): void { App::setBaseDir(PROJECT_ROOT); - $this->webAppAdapter = new WebAppAdapter(); + $this->webAppAdapter = new WebAppAdapter($this->createContext()); } public function tearDown(): void diff --git a/tests/Unit/App/AppTest.php b/tests/Unit/App/AppTest.php index 4b37cde7..97a742eb 100644 --- a/tests/Unit/App/AppTest.php +++ b/tests/Unit/App/AppTest.php @@ -7,6 +7,9 @@ use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Contracts\AppInterface; use PHPUnit\Framework\TestCase; +use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; +use Quantum\App\AppContext; use Quantum\App\App; use Quantum\Di\Di; @@ -16,6 +19,14 @@ */ class AppTest extends TestCase { + private function createContext(string $mode = AppType::WEB): AppContext + { + $container = new DiContainer(); + Di::setCurrent($container); + + return new AppContext($mode, PROJECT_ROOT, $container); + } + public function setUp(): void { parent::setUp(); @@ -28,19 +39,23 @@ public function setUp(): void public function tearDown(): void { config()->flush(); + Di::reset(); } public function testAppGetAdapter(): void { - $app = new App(new WebAppAdapter()); + $app = new App(new WebAppAdapter($this->createContext())); $this->assertInstanceOf(WebAppAdapter::class, $app->getAdapter()); $this->assertInstanceOf(AppInterface::class, $app->getAdapter()); config()->flush(); + Di::reset(); + + App::setBaseDir(PROJECT_ROOT); - $app = new App(new ConsoleAppAdapter()); + $app = new App(new ConsoleAppAdapter($this->createContext(AppType::CONSOLE))); $this->assertInstanceOf(ConsoleAppAdapter::class, $app->getAdapter()); @@ -49,7 +64,7 @@ public function testAppGetAdapter(): void public function testAppCallingValidMethod(): void { - $app = new App(new WebAppAdapter()); + $app = new App(new WebAppAdapter($this->createContext())); request()->create('GET', '/test/am/tests'); @@ -60,7 +75,7 @@ public function testAppCallingValidMethod(): void public function testAppCallingInvalidMethod(): void { - $app = new App(new WebAppAdapter()); + $app = new App(new WebAppAdapter($this->createContext())); $this->expectException(AppException::class); From aecfc7b94b978858737c0dad0c54bf796e6bd43a Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:30:34 +0400 Subject: [PATCH 54/77] [#456] Replace App::setBaseDir with App::setContext/getContext, delete unused AppTrait, consolidate createContext in AppTestCase Made-with: Cursor --- src/App/App.php | 19 +++++---- src/App/Factories/AppFactory.php | 3 +- src/App/Traits/AppTrait.php | 40 ------------------- .../App/Adapters/ConsoleAppAdapterTest.php | 21 ++-------- tests/Unit/App/Adapters/WebAppAdapterTest.php | 18 +-------- tests/Unit/App/AppTest.php | 18 +-------- .../App/Stages/LoadAppConfigStageTest.php | 15 +++---- .../App/Stages/LoadEnvironmentStageTest.php | 14 +++---- .../Unit/App/Stages/LoadHelpersStageTest.php | 11 ++--- .../Unit/App/Stages/LoadLanguageStageTest.php | 13 ++---- .../App/Stages/SetupErrorHandlerStageTest.php | 13 ++---- tests/Unit/AppTestCase.php | 14 +++++++ 12 files changed, 57 insertions(+), 142 deletions(-) delete mode 100644 src/App/Traits/AppTrait.php diff --git a/src/App/App.php b/src/App/App.php index 15adf829..cf1bf776 100644 --- a/src/App/App.php +++ b/src/App/App.php @@ -29,7 +29,7 @@ */ class App { - private static ?string $baseDir = null; + private static ?AppContext $context = null; private AppInterface $adapter; @@ -38,18 +38,23 @@ public function __construct(AppInterface $adapter) $this->adapter = $adapter; } - public static function setBaseDir(string $baseDir): void + public static function setContext(AppContext $context): void { - self::$baseDir = $baseDir; + self::$context = $context; } - public static function getBaseDir(): string + public static function getContext(): AppContext { - if (self::$baseDir === null || self::$baseDir === '') { - throw new RuntimeException('Base directory is not initialized.'); + if (self::$context === null) { + throw new RuntimeException('AppContext is not initialized.'); } - return self::$baseDir; + return self::$context; + } + + public static function getBaseDir(): string + { + return self::getContext()->getBaseDir(); } public function getAdapter(): AppInterface diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index 26739cb3..4d756e09 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -76,11 +76,10 @@ private static function createInstance(string $type, string $baseDir): App Di::setCurrent($container); $context = new AppContext($type, $baseDir, $container); + App::setContext($context); $adapterClass = self::ADAPTERS[$type]; - App::setBaseDir($baseDir); - return new App(new $adapterClass($context)); } } diff --git a/src/App/Traits/AppTrait.php b/src/App/Traits/AppTrait.php deleted file mode 100644 index dd0c8470..00000000 --- a/src/App/Traits/AppTrait.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) - * @link http://quantum.softberg.org/ - * @since 3.0.0 - */ - -namespace Quantum\App\Traits; - -/** - * Class AppTrait - * @package Quantum\App - */ -trait AppTrait -{ - /** - * Sets the app base directory - */ - public static function setBaseDir(string $baseDir): void - { - self::$baseDir = $baseDir; - } - - /** - * Gets the app base directory - */ - public static function getBaseDir(): string - { - return self::$baseDir; - } -} diff --git a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php index aa1e18fd..57cec7d2 100644 --- a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php +++ b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php @@ -4,31 +4,18 @@ use Quantum\App\Adapters\ConsoleAppAdapter; use Symfony\Component\Console\Application; -use PHPUnit\Framework\TestCase; +use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; -use Quantum\Di\DiContainer; -use Quantum\App\AppContext; -use Quantum\App\App; use Quantum\Di\Di; use Exception; use Mockery; -class ConsoleAppAdapterTest extends TestCase +class ConsoleAppAdapterTest extends AppTestCase { private $consoleAppAdapter; - private function createContext(): AppContext - { - $container = new DiContainer(); - Di::setCurrent($container); - - return new AppContext(AppType::CONSOLE, PROJECT_ROOT, $container); - } - public function setUp(): void { - App::setBaseDir(PROJECT_ROOT); - $applicationMock = Mockery::mock(Application::class)->makePartial(); $applicationMock->shouldReceive('getName')->andReturn('Qt Console Application'); $applicationMock->shouldReceive('run')->andReturn(0); @@ -52,7 +39,7 @@ public function testConsoleAppAdapterStartSuccessfully(): void { $_SERVER['argv'] = ['qt', 'list', '--quiet']; - $this->consoleAppAdapter->__construct($this->createContext()); + $this->consoleAppAdapter->__construct($this->createContext(AppType::CONSOLE)); $result = $this->consoleAppAdapter->start(); @@ -63,7 +50,7 @@ public function testConsoleAppAdapterStartFails(): void { $_SERVER['argv'] = ['qt', 'unknown', '--quiet']; - $this->consoleAppAdapter->__construct($this->createContext()); + $this->consoleAppAdapter->__construct($this->createContext(AppType::CONSOLE)); $this->expectException(Exception::class); diff --git a/tests/Unit/App/Adapters/WebAppAdapterTest.php b/tests/Unit/App/Adapters/WebAppAdapterTest.php index 45b96e09..23135d9e 100644 --- a/tests/Unit/App/Adapters/WebAppAdapterTest.php +++ b/tests/Unit/App/Adapters/WebAppAdapterTest.php @@ -3,29 +3,15 @@ namespace Quantum\Tests\Unit\App\Adapters; use Quantum\App\Adapters\WebAppAdapter; -use PHPUnit\Framework\TestCase; -use Quantum\App\Enums\AppType; -use Quantum\Di\DiContainer; -use Quantum\App\AppContext; -use Quantum\App\App; +use Quantum\Tests\Unit\AppTestCase; use Quantum\Di\Di; -class WebAppAdapterTest extends TestCase +class WebAppAdapterTest extends AppTestCase { private WebAppAdapter $webAppAdapter; - private function createContext(string $mode = AppType::WEB): AppContext - { - $container = new DiContainer(); - Di::setCurrent($container); - - return new AppContext($mode, PROJECT_ROOT, $container); - } - public function setUp(): void { - App::setBaseDir(PROJECT_ROOT); - $this->webAppAdapter = new WebAppAdapter($this->createContext()); } diff --git a/tests/Unit/App/AppTest.php b/tests/Unit/App/AppTest.php index 97a742eb..50fa979e 100644 --- a/tests/Unit/App/AppTest.php +++ b/tests/Unit/App/AppTest.php @@ -6,10 +6,8 @@ use Quantum\App\Exceptions\AppException; use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Contracts\AppInterface; -use PHPUnit\Framework\TestCase; +use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; -use Quantum\Di\DiContainer; -use Quantum\App\AppContext; use Quantum\App\App; use Quantum\Di\Di; @@ -17,23 +15,13 @@ * @runInSeparateProcess * @preserveGlobalState disabled */ -class AppTest extends TestCase +class AppTest extends AppTestCase { - private function createContext(string $mode = AppType::WEB): AppContext - { - $container = new DiContainer(); - Di::setCurrent($container); - - return new AppContext($mode, PROJECT_ROOT, $container); - } - public function setUp(): void { parent::setUp(); Di::reset(); - - App::setBaseDir(PROJECT_ROOT); } public function tearDown(): void @@ -53,8 +41,6 @@ public function testAppGetAdapter(): void config()->flush(); Di::reset(); - App::setBaseDir(PROJECT_ROOT); - $app = new App(new ConsoleAppAdapter($this->createContext(AppType::CONSOLE))); $this->assertInstanceOf(ConsoleAppAdapter::class, $app->getAdapter()); diff --git a/tests/Unit/App/Stages/LoadAppConfigStageTest.php b/tests/Unit/App/Stages/LoadAppConfigStageTest.php index 9c18ff80..6ba3e1dd 100644 --- a/tests/Unit/App/Stages/LoadAppConfigStageTest.php +++ b/tests/Unit/App/Stages/LoadAppConfigStageTest.php @@ -5,20 +5,15 @@ use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; -use PHPUnit\Framework\TestCase; -use Quantum\App\Enums\AppType; -use Quantum\App\AppContext; -use Quantum\App\App; +use Quantum\Tests\Unit\AppTestCase; use Quantum\Di\Di; -class LoadAppConfigStageTest extends TestCase +class LoadAppConfigStageTest extends AppTestCase { public function setUp(): void { Di::reset(); - App::setBaseDir(PROJECT_ROOT); - - $context = new AppContext(AppType::WEB); + $context = $this->createContext(); (new LoadHelpersStage())->process($context); (new LoadEnvironmentStage())->process($context); @@ -35,7 +30,7 @@ public function testLoadAppConfigStageImportsAppConfig(): void $this->assertFalse(config()->has('app')); $stage = new LoadAppConfigStage(); - $stage->process(new AppContext(AppType::WEB)); + $stage->process($this->createContext()); $this->assertTrue(config()->has('app')); } @@ -43,7 +38,7 @@ public function testLoadAppConfigStageImportsAppConfig(): void public function testLoadAppConfigStageSkipsIfAlreadyLoaded(): void { $stage = new LoadAppConfigStage(); - $context = new AppContext(AppType::WEB); + $context = $this->createContext(); $stage->process($context); diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php index 6e291d17..9ca2295d 100644 --- a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -4,20 +4,16 @@ use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadHelpersStage; -use PHPUnit\Framework\TestCase; +use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; -use Quantum\App\AppContext; -use Quantum\App\App; use Quantum\Di\Di; -class LoadEnvironmentStageTest extends TestCase +class LoadEnvironmentStageTest extends AppTestCase { public function setUp(): void { Di::reset(); - App::setBaseDir(PROJECT_ROOT); - - $context = new AppContext(AppType::WEB); + $context = $this->createContext(); (new LoadHelpersStage())->process($context); } @@ -30,7 +26,7 @@ public function tearDown(): void public function testLoadEnvironmentStageLoadsEnvVars(): void { $stage = new LoadEnvironmentStage(); - $stage->process(new AppContext(AppType::WEB)); + $stage->process($this->createContext()); $this->assertNotEmpty(env('APP_KEY')); } @@ -38,7 +34,7 @@ public function testLoadEnvironmentStageLoadsEnvVars(): void public function testLoadEnvironmentStageSetsMutableForConsole(): void { $stage = new LoadEnvironmentStage(); - $stage->process(new AppContext(AppType::CONSOLE)); + $stage->process($this->createContext(AppType::CONSOLE)); $this->assertNotEmpty(env('APP_KEY')); } diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php index 8a2a1dfa..8489d6b6 100644 --- a/tests/Unit/App/Stages/LoadHelpersStageTest.php +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -3,18 +3,15 @@ namespace Quantum\Tests\Unit\App\Stages; use Quantum\App\Stages\LoadHelpersStage; -use Quantum\App\Enums\AppType; -use Quantum\App\AppContext; -use Quantum\App\App; -use PHPUnit\Framework\TestCase; +use Quantum\Tests\Unit\AppTestCase; use Quantum\Di\Di; -class LoadHelpersStageTest extends TestCase +class LoadHelpersStageTest extends AppTestCase { public function setUp(): void { Di::reset(); - App::setBaseDir(PROJECT_ROOT); + $this->createContext(); } public function tearDown(): void @@ -25,7 +22,7 @@ public function tearDown(): void public function testLoadHelpersStageLoadsComponentHelpers(): void { $stage = new LoadHelpersStage(); - $stage->process(new AppContext(AppType::WEB)); + $stage->process($this->createContext()); $this->assertTrue(function_exists('config')); $this->assertTrue(function_exists('env')); diff --git a/tests/Unit/App/Stages/LoadLanguageStageTest.php b/tests/Unit/App/Stages/LoadLanguageStageTest.php index cfdce4b3..20754d04 100644 --- a/tests/Unit/App/Stages/LoadLanguageStageTest.php +++ b/tests/Unit/App/Stages/LoadLanguageStageTest.php @@ -6,20 +6,15 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Stages\LoadHelpersStage; -use PHPUnit\Framework\TestCase; -use Quantum\App\Enums\AppType; -use Quantum\App\AppContext; -use Quantum\App\App; +use Quantum\Tests\Unit\AppTestCase; use Quantum\Di\Di; -class LoadLanguageStageTest extends TestCase +class LoadLanguageStageTest extends AppTestCase { public function setUp(): void { Di::reset(); - App::setBaseDir(PROJECT_ROOT); - - $context = new AppContext(AppType::WEB); + $context = $this->createContext(); (new LoadHelpersStage())->process($context); (new LoadEnvironmentStage())->process($context); @@ -35,7 +30,7 @@ public function tearDown(): void public function testLoadLanguageStageRunsWithoutError(): void { $stage = new LoadLanguageStage(); - $stage->process(new AppContext(AppType::WEB)); + $stage->process($this->createContext()); $this->assertTrue(true); } diff --git a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php index 844864ac..c791aa09 100644 --- a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php +++ b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php @@ -6,20 +6,15 @@ use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; -use PHPUnit\Framework\TestCase; -use Quantum\App\Enums\AppType; -use Quantum\App\AppContext; -use Quantum\App\App; +use Quantum\Tests\Unit\AppTestCase; use Quantum\Di\Di; -class SetupErrorHandlerStageTest extends TestCase +class SetupErrorHandlerStageTest extends AppTestCase { public function setUp(): void { Di::reset(); - App::setBaseDir(PROJECT_ROOT); - - $context = new AppContext(AppType::WEB); + $context = $this->createContext(); (new LoadHelpersStage())->process($context); (new LoadEnvironmentStage())->process($context); @@ -37,7 +32,7 @@ public function tearDown(): void public function testSetupErrorHandlerStageRegistersHandlers(): void { $stage = new SetupErrorHandlerStage(); - $stage->process(new AppContext(AppType::WEB)); + $stage->process($this->createContext()); $errorHandler = set_error_handler(function () { }); diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 6a201cff..c650908a 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -9,10 +9,13 @@ use PHPUnit\Framework\TestCase; use Quantum\Debugger\Debugger; use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; +use Quantum\App\AppContext; use Quantum\Config\Config; use Quantum\Router\Route; use Quantum\Loader\Setup; use ReflectionClass; +use Quantum\App\App; use Quantum\Di\Di; abstract class AppTestCase extends TestCase @@ -46,6 +49,17 @@ public function tearDown(): void Di::reset(); } + protected function createContext(string $mode = AppType::WEB): AppContext + { + $container = new DiContainer(); + Di::setCurrent($container); + + $context = new AppContext($mode, PROJECT_ROOT, $container); + App::setContext($context); + + return $context; + } + protected function setPrivateProperty($object, $property, $value): void { $reflection = new ReflectionClass($object); From 8f33766e50c328b2c41ac05b6f9788713a1520d9 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:51:24 +0400 Subject: [PATCH 55/77] [#456] Register Environment in DI, migrate all callers from getInstance() Made-with: Cursor --- src/App/Stages/LoadEnvironmentStage.php | 5 ++++- src/Console/Commands/InstallToolkitCommand.php | 3 ++- src/Console/Commands/KeyGenerateCommand.php | 7 +++++-- src/Console/Commands/VersionCommand.php | 16 +++------------- src/Environment/Environment.php | 8 ++++++-- src/Environment/Helpers/env.php | 6 ++++-- src/Http/Response.php | 3 ++- src/Router/Helpers/router.php | 2 +- tests/Helpers/functions.php | 5 ++++- tests/Unit/App/Stages/LoadAppConfigStageTest.php | 16 +++++++++------- .../Unit/App/Stages/LoadEnvironmentStageTest.php | 16 ++++++++++++---- tests/Unit/App/Stages/LoadHelpersStageTest.php | 7 +++++-- tests/Unit/App/Stages/LoadLanguageStageTest.php | 13 ++++++++----- .../App/Stages/SetupErrorHandlerStageTest.php | 13 ++++++++----- tests/Unit/AppTestCase.php | 5 +---- tests/Unit/Auth/AuthTestCase.php | 3 --- .../Console/Commands/KeyGenerateCommandTest.php | 5 +++-- tests/Unit/Environment/EnvironmentTest.php | 3 ++- tests/bootstrap.php | 10 ++++++---- 19 files changed, 85 insertions(+), 61 deletions(-) diff --git a/src/App/Stages/LoadEnvironmentStage.php b/src/App/Stages/LoadEnvironmentStage.php index 14c0958d..cf3a48eb 100644 --- a/src/App/Stages/LoadEnvironmentStage.php +++ b/src/App/Stages/LoadEnvironmentStage.php @@ -24,6 +24,7 @@ use Quantum\App\AppContext; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Class LoadEnvironmentStage @@ -36,12 +37,14 @@ class LoadEnvironmentStage implements BootStageInterface */ public function process(AppContext $context): void { - $environment = Environment::getInstance(); + $environment = new Environment(); if ($context->isConsoleMode()) { $environment->setMutable(true); } $environment->load(new Setup('config', 'env')); + + Di::set(Environment::class, $environment); } } diff --git a/src/Console/Commands/InstallToolkitCommand.php b/src/Console/Commands/InstallToolkitCommand.php index edb6f18f..8cf2c36b 100644 --- a/src/Console/Commands/InstallToolkitCommand.php +++ b/src/Console/Commands/InstallToolkitCommand.php @@ -27,6 +27,7 @@ use Quantum\Console\QtCommand; use ReflectionException; use RuntimeException; +use Quantum\Di\Di; /** * Class InstallToolkitCommand @@ -72,7 +73,7 @@ public function exec(): void $password = $this->getArgument('password'); - $env = Environment::getInstance(); + $env = Di::get(Environment::class); $env->setMutable(true); diff --git a/src/Console/Commands/KeyGenerateCommand.php b/src/Console/Commands/KeyGenerateCommand.php index 2a99090d..ec2864f3 100644 --- a/src/Console/Commands/KeyGenerateCommand.php +++ b/src/Console/Commands/KeyGenerateCommand.php @@ -20,6 +20,7 @@ use Quantum\Environment\Environment; use Quantum\Console\QtCommand; use Exception; +use Quantum\Di\Di; /** * Class KeyGenerateCommand @@ -57,14 +58,16 @@ class KeyGenerateCommand extends QtCommand */ public function exec(): void { - if (Environment::getInstance()->hasKey('APP_KEY') && env('APP_KEY') !== '' && !$this->getOption('yes')) { + $environment = Di::get(Environment::class); + + if ($environment->hasKey('APP_KEY') && env('APP_KEY') !== '' && !$this->getOption('yes')) { if (!$this->confirm('The operation will remove the existing key and will create new one. Continue?')) { $this->info('Operation was canceled!'); return; } } - Environment::getInstance() + $environment ->setMutable(true) ->updateRow('APP_KEY', $this->generateRandomKey()); diff --git a/src/Console/Commands/VersionCommand.php b/src/Console/Commands/VersionCommand.php index c5a341d1..dbae807d 100644 --- a/src/Console/Commands/VersionCommand.php +++ b/src/Console/Commands/VersionCommand.php @@ -16,14 +16,8 @@ namespace Quantum\Console\Commands; -use Quantum\Environment\Exceptions\EnvException; -use Quantum\App\Exceptions\BaseException; -use Quantum\Di\Exceptions\DiException; -use Quantum\Environment\Environment; use Quantum\Console\QtCommand; use Povils\Figlet\Figlet; -use Quantum\Loader\Setup; -use ReflectionException; /** * Class VersionCommand @@ -48,24 +42,20 @@ class VersionCommand extends QtCommand /** * Executes the command and prints greetings into the terminal - * @throws DiException - * @throws EnvException - * @throws BaseException - * @throws ReflectionException */ public function exec(): void { - Environment::getInstance()->load(new Setup('config', 'env')); + $version = config()->get('app.version', 'UNKNOWN'); $figlet = new Figlet(); $renderedFiglet = $figlet ->setFontDir(assets_dir() . DS . 'shared' . DS . 'fonts' . DS . 'figlet' . DS) ->setFont('slant') - ->render('QUANTUM PHP ' . env('APP_VERSION')); + ->render('QUANTUM PHP ' . $version); $this->info($renderedFiglet); - $this->info('- - - Q U A N T U M P H P F R A M E W O R K ' . env('APP_VERSION') . ' I N S T A L L E D - - -'); + $this->info('- - - Q U A N T U M P H P F R A M E W O R K ' . $version . ' I N S T A L L E D - - -'); } } diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index 0d8ae4ef..b6a9b027 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -89,6 +89,10 @@ public function load(Setup $setup): void $appEnv = $envConfig['app_env'] ?? Env::PRODUCTION; + if (in_array($appEnv, [Env::TESTING, Env::LOCAL, Env::DEVELOPMENT], true)) { + $this->isMutable = true; + } + $this->envFile = '.env' . ($appEnv !== Env::PRODUCTION ? ".$appEnv" : ''); if (!file_exists($this->getEnvFilePath())) { @@ -195,11 +199,11 @@ private function findKeyRow(string $key): ?string /** * @return array */ - private function loadDotenvFile(bool $forceMutableReload = false): array + private function loadDotenvFile(): array { $baseDir = App::getBaseDir(); - $dotenv = ($forceMutableReload || $this->isMutable) + $dotenv = $this->isMutable ? Dotenv::createMutable($baseDir, $this->envFile) : Dotenv::createImmutable($baseDir, $this->envFile); diff --git a/src/Environment/Helpers/env.php b/src/Environment/Helpers/env.php index 6b8c259a..07b36589 100644 --- a/src/Environment/Helpers/env.php +++ b/src/Environment/Helpers/env.php @@ -13,16 +13,18 @@ */ use Quantum\Environment\Exceptions\EnvException; +use Quantum\Di\Exceptions\DiException; use Quantum\Environment\Environment; +use Quantum\Di\Di; /** * Gets the value of an environment variable * @param string $var * @param mixed|null $default * @return mixed - * @throws EnvException + * @throws EnvException|DiException|\ReflectionException */ function env(string $var, $default = null) { - return Environment::getInstance()->getValue($var, $default); + return Di::get(Environment::class)->getValue($var, $default); } diff --git a/src/Http/Response.php b/src/Http/Response.php index 11c9beef..ede4d069 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -23,6 +23,7 @@ use Quantum\Environment\Enums\Env; use Quantum\Http\Enums\StatusCode; use Exception; +use Quantum\Di\Di; /** * Class Response @@ -64,7 +65,7 @@ public function flush(): void */ public function send(): void { - if (Environment::getInstance()->getAppEnv() !== Env::TESTING) { + if (Di::get(Environment::class)->getAppEnv() !== Env::TESTING) { while (ob_get_level() > 0) { ob_end_clean(); } diff --git a/src/Router/Helpers/router.php b/src/Router/Helpers/router.php index 7b50b1bf..2f8d5c0b 100644 --- a/src/Router/Helpers/router.php +++ b/src/Router/Helpers/router.php @@ -235,7 +235,7 @@ function route_group_exists(string $name, string $module): bool */ function module_base_namespace(): string { - return Environment::getInstance()->getAppEnv() === 'testing' + return Di::get(Environment::class)->getAppEnv() === 'testing' ? 'Quantum\\Tests\\_root\\modules' : 'Modules'; } diff --git a/tests/Helpers/functions.php b/tests/Helpers/functions.php index ab608e89..eb244ddb 100644 --- a/tests/Helpers/functions.php +++ b/tests/Helpers/functions.php @@ -1,13 +1,16 @@ createContext(); + $this->context = $this->createContext(); - (new LoadHelpersStage())->process($context); - (new LoadEnvironmentStage())->process($context); + (new LoadHelpersStage())->process($this->context); + (new LoadEnvironmentStage())->process($this->context); } public function tearDown(): void @@ -30,7 +33,7 @@ public function testLoadAppConfigStageImportsAppConfig(): void $this->assertFalse(config()->has('app')); $stage = new LoadAppConfigStage(); - $stage->process($this->createContext()); + $stage->process($this->context); $this->assertTrue(config()->has('app')); } @@ -38,13 +41,12 @@ public function testLoadAppConfigStageImportsAppConfig(): void public function testLoadAppConfigStageSkipsIfAlreadyLoaded(): void { $stage = new LoadAppConfigStage(); - $context = $this->createContext(); - $stage->process($context); + $stage->process($this->context); $this->assertTrue(config()->has('app')); - $stage->process($context); + $stage->process($this->context); $this->assertTrue(config()->has('app')); } diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php index 9ca2295d..95803f19 100644 --- a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -6,16 +6,19 @@ use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; +use Quantum\App\AppContext; use Quantum\Di\Di; class LoadEnvironmentStageTest extends AppTestCase { + private AppContext $context; + public function setUp(): void { Di::reset(); - $context = $this->createContext(); + $this->context = $this->createContext(); - (new LoadHelpersStage())->process($context); + (new LoadHelpersStage())->process($this->context); } public function tearDown(): void @@ -26,15 +29,20 @@ public function tearDown(): void public function testLoadEnvironmentStageLoadsEnvVars(): void { $stage = new LoadEnvironmentStage(); - $stage->process($this->createContext()); + $stage->process($this->context); $this->assertNotEmpty(env('APP_KEY')); } public function testLoadEnvironmentStageSetsMutableForConsole(): void { + Di::reset(); + $consoleContext = $this->createContext(AppType::CONSOLE); + + (new LoadHelpersStage())->process($consoleContext); + $stage = new LoadEnvironmentStage(); - $stage->process($this->createContext(AppType::CONSOLE)); + $stage->process($consoleContext); $this->assertNotEmpty(env('APP_KEY')); } diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php index 8489d6b6..0857bad2 100644 --- a/tests/Unit/App/Stages/LoadHelpersStageTest.php +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -4,14 +4,17 @@ use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; +use Quantum\App\AppContext; use Quantum\Di\Di; class LoadHelpersStageTest extends AppTestCase { + private AppContext $context; + public function setUp(): void { Di::reset(); - $this->createContext(); + $this->context = $this->createContext(); } public function tearDown(): void @@ -22,7 +25,7 @@ public function tearDown(): void public function testLoadHelpersStageLoadsComponentHelpers(): void { $stage = new LoadHelpersStage(); - $stage->process($this->createContext()); + $stage->process($this->context); $this->assertTrue(function_exists('config')); $this->assertTrue(function_exists('env')); diff --git a/tests/Unit/App/Stages/LoadLanguageStageTest.php b/tests/Unit/App/Stages/LoadLanguageStageTest.php index 20754d04..cd694ed9 100644 --- a/tests/Unit/App/Stages/LoadLanguageStageTest.php +++ b/tests/Unit/App/Stages/LoadLanguageStageTest.php @@ -7,18 +7,21 @@ use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; +use Quantum\App\AppContext; use Quantum\Di\Di; class LoadLanguageStageTest extends AppTestCase { + private AppContext $context; + public function setUp(): void { Di::reset(); - $context = $this->createContext(); + $this->context = $this->createContext(); - (new LoadHelpersStage())->process($context); - (new LoadEnvironmentStage())->process($context); - (new LoadAppConfigStage())->process($context); + (new LoadHelpersStage())->process($this->context); + (new LoadEnvironmentStage())->process($this->context); + (new LoadAppConfigStage())->process($this->context); } public function tearDown(): void @@ -30,7 +33,7 @@ public function tearDown(): void public function testLoadLanguageStageRunsWithoutError(): void { $stage = new LoadLanguageStage(); - $stage->process($this->createContext()); + $stage->process($this->context); $this->assertTrue(true); } diff --git a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php index c791aa09..df381c38 100644 --- a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php +++ b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php @@ -7,18 +7,21 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; +use Quantum\App\AppContext; use Quantum\Di\Di; class SetupErrorHandlerStageTest extends AppTestCase { + private AppContext $context; + public function setUp(): void { Di::reset(); - $context = $this->createContext(); + $this->context = $this->createContext(); - (new LoadHelpersStage())->process($context); - (new LoadEnvironmentStage())->process($context); - (new LoadAppConfigStage())->process($context); + (new LoadHelpersStage())->process($this->context); + (new LoadEnvironmentStage())->process($this->context); + (new LoadAppConfigStage())->process($this->context); } public function tearDown(): void @@ -32,7 +35,7 @@ public function tearDown(): void public function testSetupErrorHandlerStageRegistersHandlers(): void { $stage = new SetupErrorHandlerStage(); - $stage->process($this->createContext()); + $stage->process($this->context); $errorHandler = set_error_handler(function () { }); diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index c650908a..af81417e 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -13,7 +13,6 @@ use Quantum\App\AppContext; use Quantum\Config\Config; use Quantum\Router\Route; -use Quantum\Loader\Setup; use ReflectionClass; use Quantum\App\App; use Quantum\Di\Di; @@ -26,9 +25,7 @@ public function setUp(): void { AppFactory::create(AppType::WEB, PROJECT_ROOT); - Environment::getInstance() - ->setMutable(true) - ->load(new Setup('config', 'env')); + Di::get(Environment::class)->setMutable(true); $this->fs = FileSystemFactory::get(); } diff --git a/tests/Unit/Auth/AuthTestCase.php b/tests/Unit/Auth/AuthTestCase.php index 5706fdc4..510cf800 100644 --- a/tests/Unit/Auth/AuthTestCase.php +++ b/tests/Unit/Auth/AuthTestCase.php @@ -11,7 +11,6 @@ function random_number(int $length = 10): int namespace Quantum\Tests\Unit\Auth { use Quantum\Database\Adapters\Sleekdb\SleekDbal; - use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; use Quantum\Mailer\Mailer; use Quantum\Loader\Setup; @@ -84,8 +83,6 @@ public function setUp(): void SleekDbal::connect(config()->get('database.sleekdb')); - Environment::getInstance()->load(new Setup('config', 'env')); - $this->authService = Mockery::mock(\Quantum\Auth\Contracts\AuthServiceInterface::class); $this->authService->shouldReceive('userSchema')->andReturn($this->userSchema); diff --git a/tests/Unit/Console/Commands/KeyGenerateCommandTest.php b/tests/Unit/Console/Commands/KeyGenerateCommandTest.php index ca9c58cd..2aa7e705 100644 --- a/tests/Unit/Console/Commands/KeyGenerateCommandTest.php +++ b/tests/Unit/Console/Commands/KeyGenerateCommandTest.php @@ -9,6 +9,7 @@ use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\App; +use Quantum\Di\Di; class KeyGenerateCommandTest extends AppTestCase { @@ -29,7 +30,7 @@ public function setUp(): void $this->envFilePath = App::getBaseDir() . DS . '.env.testing'; $this->originalFileContent = (string) $this->fs->get($this->envFilePath); - $this->originalEnvData = $this->getPrivateProperty(Environment::getInstance(), 'envContent'); + $this->originalEnvData = $this->getPrivateProperty(Di::get(Environment::class), 'envContent'); $this->command = new KeyGenerateCommand(); $this->command->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); @@ -39,7 +40,7 @@ public function setUp(): void public function tearDown(): void { $this->fs->put($this->envFilePath, $this->originalFileContent); - $this->setPrivateProperty(Environment::getInstance(), 'envContent', $this->originalEnvData); + $this->setPrivateProperty(Di::get(Environment::class), 'envContent', $this->originalEnvData); parent::tearDown(); } diff --git a/tests/Unit/Environment/EnvironmentTest.php b/tests/Unit/Environment/EnvironmentTest.php index 71ac72b8..70c8382b 100644 --- a/tests/Unit/Environment/EnvironmentTest.php +++ b/tests/Unit/Environment/EnvironmentTest.php @@ -5,6 +5,7 @@ use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\App; +use Quantum\Di\Di; class EnvironmentTest extends AppTestCase { @@ -14,7 +15,7 @@ public function setUp(): void { parent::setUp(); - $this->env = Environment::getInstance(); + $this->env = Di::get(Environment::class); } public function testEnvironmentGetAppEnv(): void diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 8f8fefce..ccada9ce 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -14,8 +14,10 @@ require_once __DIR__ . DS . 'Helpers' . DS . 'functions.php'; -createEnvFile(); +$envFileCreated = createEnvFile(); -register_shutdown_function(function (): void { - removeEnvFile(); -}); +if ($envFileCreated) { + register_shutdown_function(function (): void { + removeEnvFile(); + }); +} From 40a2e8f2b03c0e39063c37efd4b6424147ad9415 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:57:07 +0400 Subject: [PATCH 56/77] [#456] Remove Environment singleton, make appEnv an instance property Made-with: Cursor --- src/Environment/Environment.php | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index b6a9b027..bcf20277 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -50,24 +50,7 @@ class Environment private bool $loaded = false; - private static string $appEnv = Env::PRODUCTION; - - /** - * Instance of Environment - */ - private static ?Environment $instance = null; - - /** - * GetInstance - */ - public static function getInstance(): Environment - { - if (self::$instance === null) { - self::$instance = new self(); - } - - return self::$instance; - } + private string $appEnv = Env::PRODUCTION; public function setMutable(bool $isMutable): Environment { @@ -102,7 +85,7 @@ public function load(Setup $setup): void $this->envContent = $this->loadDotenvFile(); $this->loaded = true; - self::$appEnv = $appEnv; + $this->appEnv = $appEnv; } /** @@ -110,7 +93,7 @@ public function load(Setup $setup): void */ public function getAppEnv(): string { - return self::$appEnv; + return $this->appEnv; } /** From 2a70ce7dc7405d2589deb835777edef0da3d0c68 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:20:48 +0400 Subject: [PATCH 57/77] [#456] Use createArrayBacked for dotenv, simplify Environment key lookup, move context to parent test case Made-with: Cursor --- src/Console/Commands/KeyGenerateCommand.php | 4 +- src/Environment/Environment.php | 40 +++++-------------- src/Http/Response.php | 2 +- .../App/Stages/LoadAppConfigStageTest.php | 3 -- .../App/Stages/LoadEnvironmentStageTest.php | 3 -- .../Unit/App/Stages/LoadHelpersStageTest.php | 3 -- .../Unit/App/Stages/LoadLanguageStageTest.php | 3 -- .../App/Stages/SetupErrorHandlerStageTest.php | 3 -- tests/Unit/AppTestCase.php | 4 ++ tests/Unit/Auth/AuthTestCase.php | 3 +- 10 files changed, 19 insertions(+), 49 deletions(-) diff --git a/src/Console/Commands/KeyGenerateCommand.php b/src/Console/Commands/KeyGenerateCommand.php index ec2864f3..3e896f89 100644 --- a/src/Console/Commands/KeyGenerateCommand.php +++ b/src/Console/Commands/KeyGenerateCommand.php @@ -19,8 +19,8 @@ use Quantum\Environment\Exceptions\EnvException; use Quantum\Environment\Environment; use Quantum\Console\QtCommand; -use Exception; use Quantum\Di\Di; +use Exception; /** * Class KeyGenerateCommand @@ -60,7 +60,7 @@ public function exec(): void { $environment = Di::get(Environment::class); - if ($environment->hasKey('APP_KEY') && env('APP_KEY') !== '' && !$this->getOption('yes')) { + if ($environment->hasKey('APP_KEY') && $environment->getValue('APP_KEY') !== '' && !$this->getOption('yes')) { if (!$this->confirm('The operation will remove the existing key and will create new one. Continue?')) { $this->info('Operation was canceled!'); return; diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index bcf20277..659fd2c7 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -72,10 +72,6 @@ public function load(Setup $setup): void $appEnv = $envConfig['app_env'] ?? Env::PRODUCTION; - if (in_array($appEnv, [Env::TESTING, Env::LOCAL, Env::DEVELOPMENT], true)) { - $this->isMutable = true; - } - $this->envFile = '.env' . ($appEnv !== Env::PRODUCTION ? ".$appEnv" : ''); if (!file_exists($this->getEnvFilePath())) { @@ -120,7 +116,7 @@ public function getValue(string $key, $default = null) */ public function hasKey(string $key): bool { - return $this->findKeyRow($key) !== null; + return array_key_exists($key, $this->envContent); } /** @@ -128,7 +124,11 @@ public function hasKey(string $key): bool */ public function getRow(string $key): ?string { - return $this->findKeyRow($key); + if (!array_key_exists($key, $this->envContent)) { + return null; + } + + return $key . '=' . $this->envContent[$key]; } /** @@ -146,16 +146,16 @@ public function updateRow(string $key, ?string $value): void } $envFilePath = $this->getEnvFilePath(); - $row = $this->getRow($key); - if ($row) { + if (array_key_exists($key, $this->envContent)) { $envFileContent = fs()->get($envFilePath); if (!is_string($envFileContent)) { throw EnvException::fileNotFound($this->envFile); } - $envFileContent = preg_replace('/^' . preg_quote($row, '/') . '/m', $key . '=' . $value, $envFileContent); + $pattern = '/^' . preg_quote($key . '=' . $this->envContent[$key], '/') . '/m'; + $envFileContent = preg_replace($pattern, $key . '=' . $value, $envFileContent); fs()->put($envFilePath, (string) $envFileContent); } else { @@ -165,32 +165,12 @@ public function updateRow(string $key, ?string $value): void $this->envContent[$key] = $value; } - /** - * Finds the row by provided key - */ - private function findKeyRow(string $key): ?string - { - foreach ($this->envContent as $index => $row) { - if (preg_match('/^' . $key . '/', $index)) { - return $key . '=' . preg_quote($row, '/'); - } - } - - return null; - } - /** * @return array */ private function loadDotenvFile(): array { - $baseDir = App::getBaseDir(); - - $dotenv = $this->isMutable - ? Dotenv::createMutable($baseDir, $this->envFile) - : Dotenv::createImmutable($baseDir, $this->envFile); - - $loadedVars = $dotenv->load(); + $loadedVars = Dotenv::createArrayBacked(App::getBaseDir(), $this->envFile)->load(); return is_array($loadedVars) ? $loadedVars : []; } diff --git a/src/Http/Response.php b/src/Http/Response.php index ede4d069..06019f24 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -22,8 +22,8 @@ use Quantum\Environment\Environment; use Quantum\Environment\Enums\Env; use Quantum\Http\Enums\StatusCode; -use Exception; use Quantum\Di\Di; +use Exception; /** * Class Response diff --git a/tests/Unit/App/Stages/LoadAppConfigStageTest.php b/tests/Unit/App/Stages/LoadAppConfigStageTest.php index 2da0e585..a519727a 100644 --- a/tests/Unit/App/Stages/LoadAppConfigStageTest.php +++ b/tests/Unit/App/Stages/LoadAppConfigStageTest.php @@ -6,13 +6,10 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\AppContext; use Quantum\Di\Di; class LoadAppConfigStageTest extends AppTestCase { - private AppContext $context; - public function setUp(): void { Di::reset(); diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php index 95803f19..2d019587 100644 --- a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -6,13 +6,10 @@ use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; -use Quantum\App\AppContext; use Quantum\Di\Di; class LoadEnvironmentStageTest extends AppTestCase { - private AppContext $context; - public function setUp(): void { Di::reset(); diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php index 0857bad2..4b12941d 100644 --- a/tests/Unit/App/Stages/LoadHelpersStageTest.php +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -4,13 +4,10 @@ use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\AppContext; use Quantum\Di\Di; class LoadHelpersStageTest extends AppTestCase { - private AppContext $context; - public function setUp(): void { Di::reset(); diff --git a/tests/Unit/App/Stages/LoadLanguageStageTest.php b/tests/Unit/App/Stages/LoadLanguageStageTest.php index cd694ed9..fc71fd86 100644 --- a/tests/Unit/App/Stages/LoadLanguageStageTest.php +++ b/tests/Unit/App/Stages/LoadLanguageStageTest.php @@ -7,13 +7,10 @@ use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\AppContext; use Quantum\Di\Di; class LoadLanguageStageTest extends AppTestCase { - private AppContext $context; - public function setUp(): void { Di::reset(); diff --git a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php index df381c38..cf117fc4 100644 --- a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php +++ b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php @@ -7,13 +7,10 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\AppContext; use Quantum\Di\Di; class SetupErrorHandlerStageTest extends AppTestCase { - private AppContext $context; - public function setUp(): void { Di::reset(); diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index af81417e..69479937 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -6,6 +6,7 @@ use Quantum\App\Factories\AppFactory; use Quantum\Environment\Environment; use Quantum\Router\MatchedRoute; +use Quantum\Storage\FileSystem; use PHPUnit\Framework\TestCase; use Quantum\Debugger\Debugger; use Quantum\App\Enums\AppType; @@ -19,6 +20,9 @@ abstract class AppTestCase extends TestCase { + protected AppContext $context; + + /** @var FileSystem */ protected $fs; public function setUp(): void diff --git a/tests/Unit/Auth/AuthTestCase.php b/tests/Unit/Auth/AuthTestCase.php index 510cf800..6c40c098 100644 --- a/tests/Unit/Auth/AuthTestCase.php +++ b/tests/Unit/Auth/AuthTestCase.php @@ -10,6 +10,7 @@ function random_number(int $length = 10): int namespace Quantum\Tests\Unit\Auth { + use Quantum\Auth\Contracts\AuthServiceInterface; use Quantum\Database\Adapters\Sleekdb\SleekDbal; use Quantum\Tests\Unit\AppTestCase; use Quantum\Mailer\Mailer; @@ -83,7 +84,7 @@ public function setUp(): void SleekDbal::connect(config()->get('database.sleekdb')); - $this->authService = Mockery::mock(\Quantum\Auth\Contracts\AuthServiceInterface::class); + $this->authService = Mockery::mock(AuthServiceInterface::class); $this->authService->shouldReceive('userSchema')->andReturn($this->userSchema); From be00b1625ce046f1272f5ea2f63673572ce3a863 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:31:24 +0400 Subject: [PATCH 58/77] [#456] Add environment() helper and env check shorthand methods Made-with: Cursor --- .../Commands/InstallToolkitCommand.php | 4 +-- src/Console/Commands/KeyGenerateCommand.php | 4 +-- src/Environment/Environment.php | 25 ++++++++++++++ src/Environment/Helpers/env.php | 11 ++++++- src/Http/Response.php | 5 +-- src/Router/Helpers/router.php | 3 +- tests/Unit/AppTestCase.php | 3 +- .../Commands/KeyGenerateCommandTest.php | 6 ++-- tests/Unit/Environment/EnvironmentTest.php | 33 +++++++++++++++++-- .../Environment/Helpers/EnvHelperTest.php | 17 ++++++++++ 10 files changed, 90 insertions(+), 21 deletions(-) diff --git a/src/Console/Commands/InstallToolkitCommand.php b/src/Console/Commands/InstallToolkitCommand.php index 8cf2c36b..8f0fcd38 100644 --- a/src/Console/Commands/InstallToolkitCommand.php +++ b/src/Console/Commands/InstallToolkitCommand.php @@ -23,11 +23,9 @@ use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; -use Quantum\Environment\Environment; use Quantum\Console\QtCommand; use ReflectionException; use RuntimeException; -use Quantum\Di\Di; /** * Class InstallToolkitCommand @@ -73,7 +71,7 @@ public function exec(): void $password = $this->getArgument('password'); - $env = Di::get(Environment::class); + $env = environment(); $env->setMutable(true); diff --git a/src/Console/Commands/KeyGenerateCommand.php b/src/Console/Commands/KeyGenerateCommand.php index 3e896f89..e18c8b34 100644 --- a/src/Console/Commands/KeyGenerateCommand.php +++ b/src/Console/Commands/KeyGenerateCommand.php @@ -17,9 +17,7 @@ namespace Quantum\Console\Commands; use Quantum\Environment\Exceptions\EnvException; -use Quantum\Environment\Environment; use Quantum\Console\QtCommand; -use Quantum\Di\Di; use Exception; /** @@ -58,7 +56,7 @@ class KeyGenerateCommand extends QtCommand */ public function exec(): void { - $environment = Di::get(Environment::class); + $environment = environment(); if ($environment->hasKey('APP_KEY') && $environment->getValue('APP_KEY') !== '' && !$this->getOption('yes')) { if (!$this->confirm('The operation will remove the existing key and will create new one. Continue?')) { diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index 659fd2c7..3417ccf2 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -92,6 +92,31 @@ public function getAppEnv(): string return $this->appEnv; } + public function isProduction(): bool + { + return $this->appEnv === Env::PRODUCTION; + } + + public function isStaging(): bool + { + return $this->appEnv === Env::STAGING; + } + + public function isDevelopment(): bool + { + return $this->appEnv === Env::DEVELOPMENT; + } + + public function isTesting(): bool + { + return $this->appEnv === Env::TESTING; + } + + public function isLocal(): bool + { + return $this->appEnv === Env::LOCAL; + } + /** * Gets the environment variable value * @param null|mixed $default diff --git a/src/Environment/Helpers/env.php b/src/Environment/Helpers/env.php index 07b36589..6ab61e13 100644 --- a/src/Environment/Helpers/env.php +++ b/src/Environment/Helpers/env.php @@ -17,6 +17,15 @@ use Quantum\Environment\Environment; use Quantum\Di\Di; +/** + * Gets the Environment instance from DI + * @throws DiException|\ReflectionException + */ +function environment(): Environment +{ + return Di::get(Environment::class); +} + /** * Gets the value of an environment variable * @param string $var @@ -26,5 +35,5 @@ */ function env(string $var, $default = null) { - return Di::get(Environment::class)->getValue($var, $default); + return environment()->getValue($var, $default); } diff --git a/src/Http/Response.php b/src/Http/Response.php index 06019f24..5a452b35 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -19,10 +19,7 @@ use Quantum\Http\Traits\Response\Header; use Quantum\Http\Traits\Response\Status; use Quantum\Http\Traits\Response\Body; -use Quantum\Environment\Environment; -use Quantum\Environment\Enums\Env; use Quantum\Http\Enums\StatusCode; -use Quantum\Di\Di; use Exception; /** @@ -65,7 +62,7 @@ public function flush(): void */ public function send(): void { - if (Di::get(Environment::class)->getAppEnv() !== Env::TESTING) { + if (!environment()->isTesting()) { while (ob_get_level() > 0) { ob_end_clean(); } diff --git a/src/Router/Helpers/router.php b/src/Router/Helpers/router.php index 2f8d5c0b..04145a26 100644 --- a/src/Router/Helpers/router.php +++ b/src/Router/Helpers/router.php @@ -13,7 +13,6 @@ */ use Quantum\Di\Exceptions\DiException; -use Quantum\Environment\Environment; use Quantum\Router\RouteCollection; use Quantum\Router\Route; use Quantum\Di\Di; @@ -235,7 +234,7 @@ function route_group_exists(string $name, string $module): bool */ function module_base_namespace(): string { - return Di::get(Environment::class)->getAppEnv() === 'testing' + return environment()->isTesting() ? 'Quantum\\Tests\\_root\\modules' : 'Modules'; } diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 69479937..a4674793 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -4,7 +4,6 @@ use Quantum\Storage\Factories\FileSystemFactory; use Quantum\App\Factories\AppFactory; -use Quantum\Environment\Environment; use Quantum\Router\MatchedRoute; use Quantum\Storage\FileSystem; use PHPUnit\Framework\TestCase; @@ -29,7 +28,7 @@ public function setUp(): void { AppFactory::create(AppType::WEB, PROJECT_ROOT); - Di::get(Environment::class)->setMutable(true); + environment()->setMutable(true); $this->fs = FileSystemFactory::get(); } diff --git a/tests/Unit/Console/Commands/KeyGenerateCommandTest.php b/tests/Unit/Console/Commands/KeyGenerateCommandTest.php index 2aa7e705..3c960e1b 100644 --- a/tests/Unit/Console/Commands/KeyGenerateCommandTest.php +++ b/tests/Unit/Console/Commands/KeyGenerateCommandTest.php @@ -6,10 +6,8 @@ use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Console\Helper\HelperSet; use Quantum\Console\Commands\KeyGenerateCommand; -use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\App; -use Quantum\Di\Di; class KeyGenerateCommandTest extends AppTestCase { @@ -30,7 +28,7 @@ public function setUp(): void $this->envFilePath = App::getBaseDir() . DS . '.env.testing'; $this->originalFileContent = (string) $this->fs->get($this->envFilePath); - $this->originalEnvData = $this->getPrivateProperty(Di::get(Environment::class), 'envContent'); + $this->originalEnvData = $this->getPrivateProperty(environment(), 'envContent'); $this->command = new KeyGenerateCommand(); $this->command->setHelperSet(new HelperSet(['question' => new QuestionHelper()])); @@ -40,7 +38,7 @@ public function setUp(): void public function tearDown(): void { $this->fs->put($this->envFilePath, $this->originalFileContent); - $this->setPrivateProperty(Di::get(Environment::class), 'envContent', $this->originalEnvData); + $this->setPrivateProperty(environment(), 'envContent', $this->originalEnvData); parent::tearDown(); } diff --git a/tests/Unit/Environment/EnvironmentTest.php b/tests/Unit/Environment/EnvironmentTest.php index 70c8382b..8ee614c6 100644 --- a/tests/Unit/Environment/EnvironmentTest.php +++ b/tests/Unit/Environment/EnvironmentTest.php @@ -5,7 +5,6 @@ use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\App; -use Quantum\Di\Di; class EnvironmentTest extends AppTestCase { @@ -15,7 +14,7 @@ public function setUp(): void { parent::setUp(); - $this->env = Di::get(Environment::class); + $this->env = environment(); } public function testEnvironmentGetAppEnv(): void @@ -48,6 +47,36 @@ public function testEnvironmentGetRow(): void $this->assertEquals('APP_KEY=XYZ1234567890ABCDEFG123456789HIGKLMNOPQRSTUVWXYZ0123456789abcdefgh', $this->env->getRow('APP_KEY')); } + public function testEnvironmentIsTestingInTestEnv(): void + { + $this->assertTrue($this->env->isTesting()); + $this->assertFalse($this->env->isProduction()); + $this->assertFalse($this->env->isStaging()); + $this->assertFalse($this->env->isDevelopment()); + $this->assertFalse($this->env->isLocal()); + } + + public function testEnvironmentCheckMethodsWithDifferentEnvs(): void + { + $this->setPrivateProperty($this->env, 'appEnv', 'production'); + $this->assertTrue($this->env->isProduction()); + $this->assertFalse($this->env->isTesting()); + + $this->setPrivateProperty($this->env, 'appEnv', 'staging'); + $this->assertTrue($this->env->isStaging()); + $this->assertFalse($this->env->isProduction()); + + $this->setPrivateProperty($this->env, 'appEnv', 'development'); + $this->assertTrue($this->env->isDevelopment()); + $this->assertFalse($this->env->isProduction()); + + $this->setPrivateProperty($this->env, 'appEnv', 'local'); + $this->assertTrue($this->env->isLocal()); + $this->assertFalse($this->env->isProduction()); + + $this->setPrivateProperty($this->env, 'appEnv', 'testing'); + } + public function testEnvironmentAddAndUpdateRow(): void { $envFilePath = App::getBaseDir() . DS . '.env.testing'; diff --git a/tests/Unit/Environment/Helpers/EnvHelperTest.php b/tests/Unit/Environment/Helpers/EnvHelperTest.php index 42055684..8f0a839d 100644 --- a/tests/Unit/Environment/Helpers/EnvHelperTest.php +++ b/tests/Unit/Environment/Helpers/EnvHelperTest.php @@ -2,10 +2,21 @@ namespace Quantum\Tests\Unit\Environment\Helpers; +use Quantum\Environment\Environment; use Quantum\Tests\Unit\AppTestCase; class EnvHelperTest extends AppTestCase { + public function testEnvironmentHelperReturnsInstance(): void + { + $this->assertInstanceOf(Environment::class, environment()); + } + + public function testEnvironmentHelperReturnsSameInstance(): void + { + $this->assertSame(environment(), environment()); + } + public function testGetEnvValue(): void { $this->assertNotNull(env('APP_KEY')); @@ -14,4 +25,10 @@ public function testGetEnvValue(): void $this->assertEquals('TRUE', env('DEBUG')); } + + public function testGetEnvValueWithDefault(): void + { + $this->assertNull(env('NON_EXISTING')); + $this->assertEquals('fallback', env('NON_EXISTING', 'fallback')); + } } From c50c0b9d19a1e5508bd7bedfe85eee28e611ea8f Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:56:13 +0400 Subject: [PATCH 59/77] [#456] Guard current_module() against early boot when Request is not yet in DI During boot stages, Setup constructor calls current_module() which calls request(). Since Request is DI-managed, this triggers its construction, which in turn resolves FileSystemFactory -> Setup -> current_module() -> request(), causing a circular dependency. Add Di::has(Request::class) check in current_module() so it safely returns null when no Request instance exists. Restore Setup constructor to eagerly resolve current_module() and simplify getModule() back to a plain getter. Made-with: Cursor --- src/Loader/Setup.php | 1 - src/Router/Helpers/router.php | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Loader/Setup.php b/src/Loader/Setup.php index dabfbe4c..1b8a76ac 100644 --- a/src/Loader/Setup.php +++ b/src/Loader/Setup.php @@ -36,7 +36,6 @@ class Setup protected string $exceptionMessage; /** - * Setup constructor. * @throws DiException|ReflectionException */ public function __construct(?string $pathPrefix = null, ?string $fileName = null, bool $hierarchical = true, ?string $module = null, ?string $exceptionMessage = null) diff --git a/src/Router/Helpers/router.php b/src/Router/Helpers/router.php index 04145a26..beeba809 100644 --- a/src/Router/Helpers/router.php +++ b/src/Router/Helpers/router.php @@ -15,6 +15,7 @@ use Quantum\Di\Exceptions\DiException; use Quantum\Router\RouteCollection; use Quantum\Router\Route; +use Quantum\Http\Request; use Quantum\Di\Di; /** @@ -36,6 +37,10 @@ function current_middlewares(): ?array */ function current_module(): ?string { + if (!Di::has(Request::class)) { + return null; + } + $matchedRoute = request()->getMatchedRoute(); return $matchedRoute ? $matchedRoute->getRoute()->getModule() : null; From 8d245a6cee347c4921446daf728e00208a473709 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:12:47 +0400 Subject: [PATCH 60/77] [#456] Add getRoutes() accessor to AppContext and tests for all typed accessors Made-with: Cursor --- src/App/AppContext.php | 23 +++------- tests/Unit/App/AppContextTest.php | 76 ++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/App/AppContext.php b/src/App/AppContext.php index 1bb68509..1222df8e 100644 --- a/src/App/AppContext.php +++ b/src/App/AppContext.php @@ -16,15 +16,14 @@ namespace Quantum\App; -use Quantum\Di\Exceptions\DiException; use Quantum\Environment\Environment; +use Quantum\Router\RouteCollection; use Quantum\App\Enums\AppType; +use InvalidArgumentException; use Quantum\Di\DiContainer; use Quantum\Config\Config; use Quantum\Http\Response; use Quantum\Http\Request; -use InvalidArgumentException; -use ReflectionException; use RuntimeException; /** @@ -75,43 +74,35 @@ public function isConsoleMode(): bool return $this->mode === AppType::CONSOLE; } - /** - * @throws DiException|ReflectionException - */ public function getEnvironment(): Environment { return $this->resolveFromContainer(Environment::class); } - /** - * @throws DiException|ReflectionException - */ public function getConfig(): Config { return $this->resolveFromContainer(Config::class); } - /** - * @throws DiException|ReflectionException - */ public function getRequest(): Request { return $this->resolveFromContainer(Request::class); } - /** - * @throws DiException|ReflectionException - */ public function getResponse(): Response { return $this->resolveFromContainer(Response::class); } + public function getRoutes(): RouteCollection + { + return $this->resolveFromContainer(RouteCollection::class); + } + /** * @template T of object * @param class-string $class * @return T - * @throws DiException|ReflectionException */ private function resolveFromContainer(string $class) { diff --git a/tests/Unit/App/AppContextTest.php b/tests/Unit/App/AppContextTest.php index 1b7bff35..eeabb463 100644 --- a/tests/Unit/App/AppContextTest.php +++ b/tests/Unit/App/AppContextTest.php @@ -2,11 +2,18 @@ namespace Quantum\Tests\Unit\App; +use Quantum\Router\RouteCollection; +use Quantum\Environment\Environment; +use PHPUnit\Framework\TestCase; use Quantum\App\Enums\AppType; +use InvalidArgumentException; use Quantum\Di\DiContainer; use Quantum\App\AppContext; -use PHPUnit\Framework\TestCase; -use InvalidArgumentException; +use Quantum\Config\Config; +use Quantum\Http\Response; +use Quantum\Http\Request; +use RuntimeException; +use Mockery; class AppContextTest extends TestCase { @@ -64,4 +71,69 @@ public function testAppContextContainerDefaultsToNull(): void $this->assertNull($context->getContainer()); } + + public function testAppContextGetEnvironment(): void + { + $container = new DiContainer(); + $environment = Mockery::mock(Environment::class); + $container->set(Environment::class, $environment); + + $context = new AppContext(AppType::WEB, '/tmp', $container); + + $this->assertSame($environment, $context->getEnvironment()); + } + + public function testAppContextGetConfig(): void + { + $container = new DiContainer(); + $config = Mockery::mock(Config::class); + $container->set(Config::class, $config); + + $context = new AppContext(AppType::WEB, '/tmp', $container); + + $this->assertSame($config, $context->getConfig()); + } + + public function testAppContextGetRequest(): void + { + $container = new DiContainer(); + $request = Mockery::mock(Request::class); + $container->set(Request::class, $request); + + $context = new AppContext(AppType::WEB, '/tmp', $container); + + $this->assertSame($request, $context->getRequest()); + } + + public function testAppContextGetResponse(): void + { + $container = new DiContainer(); + $response = Mockery::mock(Response::class); + $container->set(Response::class, $response); + + $context = new AppContext(AppType::WEB, '/tmp', $container); + + $this->assertSame($response, $context->getResponse()); + } + + public function testAppContextGetRoutes(): void + { + $container = new DiContainer(); + $routes = new RouteCollection(); + $container->set(RouteCollection::class, $routes); + + $context = new AppContext(AppType::WEB, '/tmp', $container); + + $this->assertSame($routes, $context->getRoutes()); + } + + public function testAppContextAccessorThrowsWhenContainerIsNull(): void + { + $context = new AppContext(AppType::WEB); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('DiContainer is not set on AppContext.'); + + $context->getEnvironment(); + } } From 6ab9dc50043a170cc4c7fe71f6c7588f3e05690d Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:32:28 +0400 Subject: [PATCH 61/77] [#452] Add explicit Di::register() guard to all 12 factory get() methods Made-with: Cursor --- src/Archive/Factories/ArchiveFactory.php | 4 ++++ src/Auth/Factories/AuthFactory.php | 4 ++++ src/Cache/Factories/CacheFactory.php | 4 ++++ src/Captcha/Factories/CaptchaFactory.php | 4 ++++ src/Encryption/Factories/CryptorFactory.php | 4 ++++ src/Lang/Factories/LangFactory.php | 4 ++++ src/Logger/Factories/LoggerFactory.php | 4 ++++ src/Mailer/Factories/MailerFactory.php | 4 ++++ src/Renderer/Factories/RendererFactory.php | 4 ++++ src/Session/Factories/SessionFactory.php | 4 ++++ src/Storage/Factories/FileSystemFactory.php | 4 ++++ src/View/Factories/ViewFactory.php | 4 ++++ 12 files changed, 48 insertions(+) diff --git a/src/Archive/Factories/ArchiveFactory.php b/src/Archive/Factories/ArchiveFactory.php index a27b8e89..48972202 100644 --- a/src/Archive/Factories/ArchiveFactory.php +++ b/src/Archive/Factories/ArchiveFactory.php @@ -46,6 +46,10 @@ class ArchiveFactory */ public static function get(string $type = ArchiveType::PHAR): Archive { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($type); } diff --git a/src/Auth/Factories/AuthFactory.php b/src/Auth/Factories/AuthFactory.php index 00f6c016..80dff5f2 100644 --- a/src/Auth/Factories/AuthFactory.php +++ b/src/Auth/Factories/AuthFactory.php @@ -61,6 +61,10 @@ class AuthFactory */ public static function get(?string $adapter = null): Auth { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Cache/Factories/CacheFactory.php b/src/Cache/Factories/CacheFactory.php index 4b46fca1..4b62610c 100644 --- a/src/Cache/Factories/CacheFactory.php +++ b/src/Cache/Factories/CacheFactory.php @@ -54,6 +54,10 @@ class CacheFactory */ public static function get(?string $adapter = null): Cache { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Captcha/Factories/CaptchaFactory.php b/src/Captcha/Factories/CaptchaFactory.php index 05a2b5ec..57efd335 100644 --- a/src/Captcha/Factories/CaptchaFactory.php +++ b/src/Captcha/Factories/CaptchaFactory.php @@ -54,6 +54,10 @@ class CaptchaFactory */ public static function get(?string $adapter = null): Captcha { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Encryption/Factories/CryptorFactory.php b/src/Encryption/Factories/CryptorFactory.php index 7096b23f..6a9609e5 100644 --- a/src/Encryption/Factories/CryptorFactory.php +++ b/src/Encryption/Factories/CryptorFactory.php @@ -46,6 +46,10 @@ class CryptorFactory */ public static function get(string $type = CryptorType::SYMMETRIC): Cryptor { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($type); } diff --git a/src/Lang/Factories/LangFactory.php b/src/Lang/Factories/LangFactory.php index 4367b13e..7a4c0b7e 100644 --- a/src/Lang/Factories/LangFactory.php +++ b/src/Lang/Factories/LangFactory.php @@ -39,6 +39,10 @@ class LangFactory */ public static function get(): Lang { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve(); } diff --git a/src/Logger/Factories/LoggerFactory.php b/src/Logger/Factories/LoggerFactory.php index e2502e94..b8884c5e 100644 --- a/src/Logger/Factories/LoggerFactory.php +++ b/src/Logger/Factories/LoggerFactory.php @@ -53,6 +53,10 @@ class LoggerFactory */ public static function get(?string $adapter = null): Logger { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Mailer/Factories/MailerFactory.php b/src/Mailer/Factories/MailerFactory.php index bc08f1c0..e98e48fb 100644 --- a/src/Mailer/Factories/MailerFactory.php +++ b/src/Mailer/Factories/MailerFactory.php @@ -58,6 +58,10 @@ class MailerFactory */ public static function get(?string $adapter = null): Mailer { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Renderer/Factories/RendererFactory.php b/src/Renderer/Factories/RendererFactory.php index 10df8b72..793409ab 100644 --- a/src/Renderer/Factories/RendererFactory.php +++ b/src/Renderer/Factories/RendererFactory.php @@ -53,6 +53,10 @@ class RendererFactory */ public static function get(?string $adapter = null): Renderer { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Session/Factories/SessionFactory.php b/src/Session/Factories/SessionFactory.php index ec7d257d..6d31abb3 100644 --- a/src/Session/Factories/SessionFactory.php +++ b/src/Session/Factories/SessionFactory.php @@ -50,6 +50,10 @@ class SessionFactory */ public static function get(?string $adapter = null): Session { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/Storage/Factories/FileSystemFactory.php b/src/Storage/Factories/FileSystemFactory.php index 74668989..3c20a8ee 100644 --- a/src/Storage/Factories/FileSystemFactory.php +++ b/src/Storage/Factories/FileSystemFactory.php @@ -64,6 +64,10 @@ class FileSystemFactory */ public static function get(?string $adapter = null): FileSystem { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve($adapter); } diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index 14c57ab0..8c21a664 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -40,6 +40,10 @@ class ViewFactory */ public static function get(): QtView { + if (!Di::isRegistered(self::class)) { + Di::register(self::class); + } + return Di::get(self::class)->resolve(); } From 355836b9a159cf79a921dfe17e288163ccea9ddf Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:38:27 +0400 Subject: [PATCH 62/77] [#452] Add lazy registration guards to all helper functions Made-with: Cursor --- src/Asset/Helpers/asset.php | 6 +++++- src/Config/Helpers/config.php | 4 ++++ src/Csrf/Helpers/csrf.php | 4 ++++ src/Environment/Helpers/env.php | 4 ++++ src/Environment/Helpers/server.php | 8 ++++++-- src/Hook/Helpers/hook.php | 4 ++++ src/Http/Helpers/http.php | 8 ++++++++ src/View/Helpers/view.php | 4 ++++ 8 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Asset/Helpers/asset.php b/src/Asset/Helpers/asset.php index db3cc6aa..d5923eae 100644 --- a/src/Asset/Helpers/asset.php +++ b/src/Asset/Helpers/asset.php @@ -23,6 +23,10 @@ */ function asset(): AssetManager { + if (!Di::isRegistered(AssetManager::class)) { + Di::register(AssetManager::class); + } + return Di::get(AssetManager::class); } @@ -32,5 +36,5 @@ function asset(): AssetManager */ function assets(string $type): void { - Di::get(AssetManager::class)->dump(AssetManager::STORES[$type]); + asset()->dump(AssetManager::STORES[$type]); } diff --git a/src/Config/Helpers/config.php b/src/Config/Helpers/config.php index f4395648..a039f587 100644 --- a/src/Config/Helpers/config.php +++ b/src/Config/Helpers/config.php @@ -22,5 +22,9 @@ */ function config(): Config { + if (!Di::isRegistered(Config::class)) { + Di::register(Config::class); + } + return Di::get(Config::class); } diff --git a/src/Csrf/Helpers/csrf.php b/src/Csrf/Helpers/csrf.php index 5a1d6f4c..9047e57d 100644 --- a/src/Csrf/Helpers/csrf.php +++ b/src/Csrf/Helpers/csrf.php @@ -22,6 +22,10 @@ */ function csrf(): Csrf { + if (!Di::isRegistered(Csrf::class)) { + Di::register(Csrf::class); + } + return Di::get(Csrf::class); } diff --git a/src/Environment/Helpers/env.php b/src/Environment/Helpers/env.php index 6ab61e13..ed396793 100644 --- a/src/Environment/Helpers/env.php +++ b/src/Environment/Helpers/env.php @@ -23,6 +23,10 @@ */ function environment(): Environment { + if (!Di::isRegistered(Environment::class)) { + Di::register(Environment::class); + } + return Di::get(Environment::class); } diff --git a/src/Environment/Helpers/server.php b/src/Environment/Helpers/server.php index 4b93c7b1..f8486b80 100644 --- a/src/Environment/Helpers/server.php +++ b/src/Environment/Helpers/server.php @@ -21,12 +21,16 @@ */ function server(): Server { + if (!Di::isRegistered(Server::class)) { + Di::register(Server::class); + } + return Di::get(Server::class); } function get_user_ip(): ?string { - return Di::get(Server::class)->ip(); + return server()->ip(); } if (!function_exists('getallheaders')) { @@ -37,6 +41,6 @@ function get_user_ip(): ?string */ function getallheaders(): array { - return Di::get(Server::class)->getAllHeaders(); + return server()->getAllHeaders(); } } diff --git a/src/Hook/Helpers/hook.php b/src/Hook/Helpers/hook.php index 5b745457..709d283e 100644 --- a/src/Hook/Helpers/hook.php +++ b/src/Hook/Helpers/hook.php @@ -22,5 +22,9 @@ */ function hook(): HookManager { + if (!Di::isRegistered(HookManager::class)) { + Di::register(HookManager::class); + } + return Di::get(HookManager::class); } diff --git a/src/Http/Helpers/http.php b/src/Http/Helpers/http.php index 799aab6a..2832d5db 100644 --- a/src/Http/Helpers/http.php +++ b/src/Http/Helpers/http.php @@ -29,6 +29,10 @@ */ function request(): Request { + if (!Di::isRegistered(Request::class)) { + Di::register(Request::class); + } + return Di::get(Request::class); } @@ -38,6 +42,10 @@ function request(): Request */ function response(): Response { + if (!Di::isRegistered(Response::class)) { + Di::register(Response::class); + } + return Di::get(Response::class); } diff --git a/src/View/Helpers/view.php b/src/View/Helpers/view.php index f1b92567..6a11df00 100644 --- a/src/View/Helpers/view.php +++ b/src/View/Helpers/view.php @@ -73,6 +73,10 @@ function raw_param($value): RawParam */ function debugbar(): ?string { + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { From 022285cdbf3ed3e7e93392ad23e1670324d7221c Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:44:10 +0400 Subject: [PATCH 63/77] [#452] Add explicit registration for Loader, ErrorHandler, Request, Response in boot stages Made-with: Cursor --- src/App/Stages/InitHttpStage.php | 8 ++++++++ src/App/Stages/LoadHelpersStage.php | 4 ++++ src/App/Stages/SetupErrorHandlerStage.php | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/src/App/Stages/InitHttpStage.php b/src/App/Stages/InitHttpStage.php index a020e809..f62bbf48 100644 --- a/src/App/Stages/InitHttpStage.php +++ b/src/App/Stages/InitHttpStage.php @@ -35,6 +35,14 @@ class InitHttpStage implements BootStageInterface */ public function process(AppContext $context): void { + if (!Di::isRegistered(Request::class)) { + Di::register(Request::class); + } + + if (!Di::isRegistered(Response::class)) { + Di::register(Response::class); + } + Di::get(Request::class); Di::get(Response::class); } diff --git a/src/App/Stages/LoadHelpersStage.php b/src/App/Stages/LoadHelpersStage.php index 8bf70697..12b13888 100644 --- a/src/App/Stages/LoadHelpersStage.php +++ b/src/App/Stages/LoadHelpersStage.php @@ -35,6 +35,10 @@ class LoadHelpersStage implements BootStageInterface */ public function process(AppContext $context): void { + if (!Di::isRegistered(Loader::class)) { + Di::register(Loader::class); + } + $loader = Di::get(Loader::class); $this->loadComponentHelpers($loader); diff --git a/src/App/Stages/SetupErrorHandlerStage.php b/src/App/Stages/SetupErrorHandlerStage.php index 4fc877fb..b39a80ed 100644 --- a/src/App/Stages/SetupErrorHandlerStage.php +++ b/src/App/Stages/SetupErrorHandlerStage.php @@ -37,6 +37,10 @@ class SetupErrorHandlerStage implements BootStageInterface */ public function process(AppContext $context): void { + if (!Di::isRegistered(ErrorHandler::class)) { + Di::register(ErrorHandler::class); + } + Di::get(ErrorHandler::class)->setup(LoggerFactory::get()); } } From ba7f235327628dac755f0e50801b6149f65f6860 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:55:00 +0400 Subject: [PATCH 64/77] [#452] Add registration guards for Loader, ModuleLoader, Debugger, ViewCache, Database, MailTrap at all call sites Made-with: Cursor --- src/App/Adapters/WebAppAdapter.php | 8 ++++++++ src/App/Traits/WebAppTrait.php | 8 ++++++++ src/Config/Config.php | 8 ++++++++ src/Console/Commands/OpenApiCommand.php | 4 ++++ src/Console/Commands/RouteListCommand.php | 4 ++++ src/Database/Traits/RelationalTrait.php | 4 ++++ src/Database/Traits/TransactionTrait.php | 4 ++++ src/Environment/Environment.php | 4 ++++ src/Logger/Adapters/MessageAdapter.php | 4 ++++ src/Mailer/Traits/MailerTrait.php | 4 ++++ src/Migration/MigrationManager.php | 4 ++++ src/Model/Factories/ModelFactory.php | 4 ++++ src/Module/ModuleManager.php | 4 ++++ src/Storage/UploadedFile.php | 4 ++++ src/View/Factories/ViewFactory.php | 8 ++++++++ 15 files changed, 76 insertions(+) diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 23bd9d39..7c5fd805 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -81,6 +81,10 @@ public function start(): ?int $this->initializeDebugger(); + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + $moduleLoader = Di::get(ModuleLoader::class); $builder = new RouteBuilder(); @@ -105,6 +109,10 @@ public function start(): ?int (new LoadLanguageStage())->process($this->context); + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { $debugger->addToStoreCell(Debugger::HOOKS, 'info', hook()->getRegistered()); diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index bab03e82..d74fa5a9 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -37,6 +37,10 @@ trait WebAppTrait */ private function initializeDebugger(): void { + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $debugger = Di::get(Debugger::class); $debugger->initStore(); } @@ -48,6 +52,10 @@ private function initializeDebugger(): void */ private function setupViewCache(): ViewCache { + if (!Di::isRegistered(ViewCache::class)) { + Di::register(ViewCache::class); + } + $viewCache = Di::get(ViewCache::class); if ($viewCache->isEnabled()) { diff --git a/src/Config/Config.php b/src/Config/Config.php index 6657912b..9e9862e0 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -44,6 +44,10 @@ public function load(Setup $setup): void return; } + if (!Di::isRegistered(Loader::class)) { + Di::register(Loader::class); + } + $this->configs = new Data(Di::get(Loader::class)->setup($setup)->load()); } @@ -61,6 +65,10 @@ public function import(Setup $setup): void throw ConfigException::configCollision($fileName); } + if (!Di::isRegistered(Loader::class)) { + Di::register(Loader::class); + } + if (!$this->configs) { $this->configs = new Data([$fileName => Di::get(Loader::class)->setup($setup)->load()]); } else { diff --git a/src/Console/Commands/OpenApiCommand.php b/src/Console/Commands/OpenApiCommand.php index 08b37d70..856fb3e4 100644 --- a/src/Console/Commands/OpenApiCommand.php +++ b/src/Console/Commands/OpenApiCommand.php @@ -93,6 +93,10 @@ public function __construct() */ public function exec(): void { + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + $moduleLoader = Di::get(ModuleLoader::class); $builder = new RouteBuilder(); diff --git a/src/Console/Commands/RouteListCommand.php b/src/Console/Commands/RouteListCommand.php index 7940dd3a..40005c6d 100644 --- a/src/Console/Commands/RouteListCommand.php +++ b/src/Console/Commands/RouteListCommand.php @@ -59,6 +59,10 @@ class RouteListCommand extends QtCommand public function exec(): void { try { + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + $moduleLoader = Di::get(ModuleLoader::class); $builder = new RouteBuilder(); diff --git a/src/Database/Traits/RelationalTrait.php b/src/Database/Traits/RelationalTrait.php index 35f7aac8..efabbc25 100644 --- a/src/Database/Traits/RelationalTrait.php +++ b/src/Database/Traits/RelationalTrait.php @@ -87,6 +87,10 @@ public static function queryLog(): array */ protected static function resolveQuery(string $method, string $query = '', array $parameters = []) { + if (!Di::isRegistered(Database::class)) { + Di::register(Database::class); + } + return Di::get(Database::class)->getOrmClass()::$method($query, $parameters); } } diff --git a/src/Database/Traits/TransactionTrait.php b/src/Database/Traits/TransactionTrait.php index 3ea6f08e..c88e6356 100644 --- a/src/Database/Traits/TransactionTrait.php +++ b/src/Database/Traits/TransactionTrait.php @@ -64,6 +64,10 @@ public static function rollback(): void */ protected static function resolveTransaction(string $method) { + if (!Di::isRegistered(Database::class)) { + Di::register(Database::class); + } + $db = Di::get(Database::class)->getOrmClass(); if (!method_exists($db, $method)) { diff --git a/src/Environment/Environment.php b/src/Environment/Environment.php index 3417ccf2..eaf6628e 100644 --- a/src/Environment/Environment.php +++ b/src/Environment/Environment.php @@ -68,6 +68,10 @@ public function load(Setup $setup): void return; } + if (!Di::isRegistered(Loader::class)) { + Di::register(Loader::class); + } + $envConfig = Di::get(Loader::class)->setup($setup)->load(); $appEnv = $envConfig['app_env'] ?? Env::PRODUCTION; diff --git a/src/Logger/Adapters/MessageAdapter.php b/src/Logger/Adapters/MessageAdapter.php index c13c2b81..f880e466 100644 --- a/src/Logger/Adapters/MessageAdapter.php +++ b/src/Logger/Adapters/MessageAdapter.php @@ -41,6 +41,10 @@ public function report(string $level, string $message, ?array $context = []): vo { $tab = $context['tab'] ?? Debugger::MESSAGES; + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { diff --git a/src/Mailer/Traits/MailerTrait.php b/src/Mailer/Traits/MailerTrait.php index e9118509..942e8a32 100644 --- a/src/Mailer/Traits/MailerTrait.php +++ b/src/Mailer/Traits/MailerTrait.php @@ -267,6 +267,10 @@ private function saveEmail(): bool return false; } + if (!Di::isRegistered(MailTrap::class)) { + Di::register(MailTrap::class); + } + return Di::get(MailTrap::class)->saveMessage($messageId, $this->getMessageContent()); } diff --git a/src/Migration/MigrationManager.php b/src/Migration/MigrationManager.php index 08569b01..aef60cb0 100644 --- a/src/Migration/MigrationManager.php +++ b/src/Migration/MigrationManager.php @@ -77,6 +77,10 @@ public function __construct() { $this->fs = FileSystemFactory::get(); + if (!Di::isRegistered(Database::class)) { + Di::register(Database::class); + } + $this->db = Di::get(Database::class); $this->tableFactory = new TableFactory(); diff --git a/src/Model/Factories/ModelFactory.php b/src/Model/Factories/ModelFactory.php index 40d05fed..52fd5ba7 100644 --- a/src/Model/Factories/ModelFactory.php +++ b/src/Model/Factories/ModelFactory.php @@ -116,6 +116,10 @@ protected static function createOrmInstance( array $foreignKeys = [], array $hidden = [] ): DbalInterface { + if (!Di::isRegistered(Database::class)) { + Di::register(Database::class); + } + $ormClass = Di::get(Database::class)->getOrmClass(); $instance = new $ormClass( diff --git a/src/Module/ModuleManager.php b/src/Module/ModuleManager.php index c28cec60..df9fc92c 100644 --- a/src/Module/ModuleManager.php +++ b/src/Module/ModuleManager.php @@ -86,6 +86,10 @@ public function addModuleConfig(): void throw ModuleException::missingModuleDirectory(); } + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + $moduleConfigs = Di::get(ModuleLoader::class)->getModuleConfigs(); foreach ($moduleConfigs as $module => $options) { diff --git a/src/Storage/UploadedFile.php b/src/Storage/UploadedFile.php index 316540d0..c56497af 100644 --- a/src/Storage/UploadedFile.php +++ b/src/Storage/UploadedFile.php @@ -397,6 +397,10 @@ protected function allowed(string $extension, string $mimeType): bool protected function loadAllowedMimeTypesFromConfig(): void { if (!config()->has('uploads')) { + if (!Di::isRegistered(Loader::class)) { + Di::register(Loader::class); + } + $loader = Di::get(Loader::class); $setup = new Setup('config', 'uploads'); $loader->setup($setup); diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index 8c21a664..059f1eed 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -53,6 +53,14 @@ public static function get(): QtView public function resolve(): QtView { if ($this->instance === null) { + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + + if (!Di::isRegistered(ViewCache::class)) { + Di::register(ViewCache::class); + } + $this->instance = new QtView( RendererFactory::get(), asset(), From ed235d8f9ecc2becdaac6db4dbcde80c107c6b5d Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:00:05 +0400 Subject: [PATCH 65/77] [#452] Enforce strict Di::get() contract - throw if dependency not registered Made-with: Cursor --- src/Di/DiContainer.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Di/DiContainer.php b/src/Di/DiContainer.php index 3eebbfad..a93fa4da 100644 --- a/src/Di/DiContainer.php +++ b/src/Di/DiContainer.php @@ -145,10 +145,7 @@ public function set(string $abstract, object $instance, bool $override = true): public function get(string $dependency, array $args = []) { if (!$this->isRegistered($dependency)) { - if (!$this->instantiable($dependency)) { - throw DiException::dependencyNotRegistered($dependency); - } - $this->register($dependency); + throw DiException::dependencyNotRegistered($dependency); } return $this->resolve($dependency, $args, true); From fcf2dd26827e744aa145c03fa2e021a5bc63eb3e Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:13:31 +0400 Subject: [PATCH 66/77] [#452] Fix test failures from strict Di::get() contract - add explicit registration in test setUp methods Made-with: Cursor --- tests/Unit/Auth/Factories/AuthFactoryTest.php | 4 ++++ tests/Unit/Cache/Factories/CacheFactoryTest.php | 4 ++++ tests/Unit/Captcha/Factories/CaptchaFactoryTest.php | 4 ++++ tests/Unit/Di/DiContainerTest.php | 8 ++++---- tests/Unit/Lang/Factories/LangFactoryTest.php | 4 ++++ tests/Unit/Logger/Adapters/MessageAdapterTest.php | 4 ++++ tests/Unit/Logger/Factories/LoggerFactoryTest.php | 4 ++++ tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php | 4 ++++ tests/Unit/Logger/LoggerTest.php | 4 ++++ tests/Unit/Mailer/Factories/MailerFactoryTest.php | 4 ++++ tests/Unit/Mailer/MailTrapTest.php | 4 ++++ tests/Unit/Module/ModuleLoaderTest.php | 4 ++++ tests/Unit/Renderer/Factories/RendererFactoryTest.php | 4 ++++ tests/Unit/ResourceCache/ViewCacheTest.php | 4 ++++ tests/Unit/Session/Factories/SessionFactoryTest.php | 4 ++++ tests/Unit/View/Factories/ViewFactoryTest.php | 9 +++++++++ tests/_root/.env.testing | 2 ++ 17 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/_root/.env.testing diff --git a/tests/Unit/Auth/Factories/AuthFactoryTest.php b/tests/Unit/Auth/Factories/AuthFactoryTest.php index 40cebb18..4fd3de8f 100644 --- a/tests/Unit/Auth/Factories/AuthFactoryTest.php +++ b/tests/Unit/Auth/Factories/AuthFactoryTest.php @@ -69,6 +69,10 @@ public function testAuthFactoryReturnsSameInstance(): void private function resetAuthFactory(): void { + if (!Di::isRegistered(AuthFactory::class)) { + Di::register(AuthFactory::class); + } + $factory = Di::get(AuthFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/Cache/Factories/CacheFactoryTest.php b/tests/Unit/Cache/Factories/CacheFactoryTest.php index 10b674e7..ef6dbf3e 100644 --- a/tests/Unit/Cache/Factories/CacheFactoryTest.php +++ b/tests/Unit/Cache/Factories/CacheFactoryTest.php @@ -83,6 +83,10 @@ public function testCacheFactoryReturnsSameInstance(): void private function resetCacheFactory(): void { + if (!Di::isRegistered(CacheFactory::class)) { + Di::register(CacheFactory::class); + } + $factory = Di::get(CacheFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php b/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php index 24aa28c8..3b3f34d5 100644 --- a/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php +++ b/tests/Unit/Captcha/Factories/CaptchaFactoryTest.php @@ -67,6 +67,10 @@ public function testAuthFactoryReturnsSameInstance(): void private function resetCaptchaFactory(): void { + if (!Di::isRegistered(CaptchaFactory::class)) { + Di::register(CaptchaFactory::class); + } + $factory = Di::get(CaptchaFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/Di/DiContainerTest.php b/tests/Unit/Di/DiContainerTest.php index 57d5ae80..f263a11a 100644 --- a/tests/Unit/Di/DiContainerTest.php +++ b/tests/Unit/Di/DiContainerTest.php @@ -193,14 +193,14 @@ public function testSetOverridesRegisteredButNotResolved(): void $this->assertSame($instance, $resolved); } - public function testGetAutoRegistersInstantiableClass(): void + public function testGetThrowsForUnregisteredInstantiableClass(): void { $this->assertFalse($this->container->isRegistered(DiException::class)); - $instance = $this->container->get(DiException::class); + $this->expectException(DiException::class); + $this->expectExceptionMessage('The dependency `Quantum\Di\Exceptions\DiException` is not registered.'); - $this->assertInstanceOf(DiException::class, $instance); - $this->assertTrue($this->container->isRegistered(DiException::class)); + $this->container->get(DiException::class); } public function testGetThrowsForNonInstantiableClass(): void diff --git a/tests/Unit/Lang/Factories/LangFactoryTest.php b/tests/Unit/Lang/Factories/LangFactoryTest.php index 8b021fb1..e003de59 100644 --- a/tests/Unit/Lang/Factories/LangFactoryTest.php +++ b/tests/Unit/Lang/Factories/LangFactoryTest.php @@ -125,6 +125,10 @@ public function testLangFactoryThrowsErrorIfNoDefaultLangFound(): void private function resetLangFactory(): void { + if (!Di::isRegistered(LangFactory::class)) { + Di::register(LangFactory::class); + } + $factory = Di::get(LangFactory::class); $this->setPrivateProperty($factory, 'instance', null); } diff --git a/tests/Unit/Logger/Adapters/MessageAdapterTest.php b/tests/Unit/Logger/Adapters/MessageAdapterTest.php index dc5644df..65cb2c50 100644 --- a/tests/Unit/Logger/Adapters/MessageAdapterTest.php +++ b/tests/Unit/Logger/Adapters/MessageAdapterTest.php @@ -16,6 +16,10 @@ public function setUp(): void { parent::setUp(); + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $this->debugger = Di::get(Debugger::class); $this->debugger->initStore(); diff --git a/tests/Unit/Logger/Factories/LoggerFactoryTest.php b/tests/Unit/Logger/Factories/LoggerFactoryTest.php index 43ae5204..077d2616 100644 --- a/tests/Unit/Logger/Factories/LoggerFactoryTest.php +++ b/tests/Unit/Logger/Factories/LoggerFactoryTest.php @@ -97,6 +97,10 @@ public function testLoggerFactoryReturnsSameInstance(): void private function resetLoggerFactory(): void { + if (!Di::isRegistered(LoggerFactory::class)) { + Di::register(LoggerFactory::class); + } + $factory = Di::get(LoggerFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php b/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php index cc1468b1..9bfa5010 100644 --- a/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php +++ b/tests/Unit/Logger/Helpers/LoggerHelperFunctionsTest.php @@ -22,6 +22,10 @@ public function setUp(): void config()->set('app.debug', true); + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $this->debugger = Di::get(Debugger::class); $this->debugger->resetStore(); diff --git a/tests/Unit/Logger/LoggerTest.php b/tests/Unit/Logger/LoggerTest.php index c5a03da7..49b18ca2 100644 --- a/tests/Unit/Logger/LoggerTest.php +++ b/tests/Unit/Logger/LoggerTest.php @@ -19,6 +19,10 @@ public function setUp(): void { parent::setUp(); + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + $this->debugger = Di::get(Debugger::class); $this->debugger->initStore(); diff --git a/tests/Unit/Mailer/Factories/MailerFactoryTest.php b/tests/Unit/Mailer/Factories/MailerFactoryTest.php index 152e6516..48fd8ccf 100644 --- a/tests/Unit/Mailer/Factories/MailerFactoryTest.php +++ b/tests/Unit/Mailer/Factories/MailerFactoryTest.php @@ -104,6 +104,10 @@ public function testMailerFactoryReturnsSameInstance(): void private function resetMailerFactory(): void { + if (!Di::isRegistered(MailerFactory::class)) { + Di::register(MailerFactory::class); + } + $factory = Di::get(MailerFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/Mailer/MailTrapTest.php b/tests/Unit/Mailer/MailTrapTest.php index c9d6a348..988181d8 100644 --- a/tests/Unit/Mailer/MailTrapTest.php +++ b/tests/Unit/Mailer/MailTrapTest.php @@ -20,6 +20,10 @@ public function setUp(): void { parent::setUp(); + if (!Di::isRegistered(MailTrap::class)) { + Di::register(MailTrap::class); + } + $this->mailTrap = Di::get(MailTrap::class); $this->filename = '2YILSA4zZk61tDdYEfGMw7lNznlhAQakjwNGr0QCq44'; diff --git a/tests/Unit/Module/ModuleLoaderTest.php b/tests/Unit/Module/ModuleLoaderTest.php index fe3ed8c2..d8e7abf0 100644 --- a/tests/Unit/Module/ModuleLoaderTest.php +++ b/tests/Unit/Module/ModuleLoaderTest.php @@ -14,6 +14,10 @@ public function setUp(): void { parent::setUp(); + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + $this->moduleLoader = Di::get(ModuleLoader::class); } diff --git a/tests/Unit/Renderer/Factories/RendererFactoryTest.php b/tests/Unit/Renderer/Factories/RendererFactoryTest.php index deb9f599..3e825118 100644 --- a/tests/Unit/Renderer/Factories/RendererFactoryTest.php +++ b/tests/Unit/Renderer/Factories/RendererFactoryTest.php @@ -74,6 +74,10 @@ public function testRendererFactoryReturnsSameInstance(): void private function resetRendererFactory(): void { + if (!Di::isRegistered(RendererFactory::class)) { + Di::register(RendererFactory::class); + } + $factory = Di::get(RendererFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/ResourceCache/ViewCacheTest.php b/tests/Unit/ResourceCache/ViewCacheTest.php index eaeca9fd..2c1c5c7a 100644 --- a/tests/Unit/ResourceCache/ViewCacheTest.php +++ b/tests/Unit/ResourceCache/ViewCacheTest.php @@ -43,6 +43,10 @@ public function setUp(): void { parent::setUp(); + if (!Di::isRegistered(ViewCache::class)) { + Di::register(ViewCache::class); + } + $this->viewCache = Di::get(ViewCache::class); $this->viewCache->setup(); } diff --git a/tests/Unit/Session/Factories/SessionFactoryTest.php b/tests/Unit/Session/Factories/SessionFactoryTest.php index f51428e5..93cbc6ca 100644 --- a/tests/Unit/Session/Factories/SessionFactoryTest.php +++ b/tests/Unit/Session/Factories/SessionFactoryTest.php @@ -123,6 +123,10 @@ public function testSessionFactoryReturnsSameInstance(): void private function resetSessionFactory(): void { + if (!Di::isRegistered(SessionFactory::class)) { + Di::register(SessionFactory::class); + } + $factory = Di::get(SessionFactory::class); $this->setPrivateProperty($factory, 'instances', []); } diff --git a/tests/Unit/View/Factories/ViewFactoryTest.php b/tests/Unit/View/Factories/ViewFactoryTest.php index e392286d..ab4c7bf0 100644 --- a/tests/Unit/View/Factories/ViewFactoryTest.php +++ b/tests/Unit/View/Factories/ViewFactoryTest.php @@ -16,6 +16,11 @@ public function setUp(): void parent::setUp(); $this->resetViewFactory(); + + if (!Di::isRegistered(ViewFactory::class)) { + Di::register(ViewFactory::class); + } + $this->viewFactory = Di::get(ViewFactory::class); } @@ -43,6 +48,10 @@ public function testProxyCalls(): void private function resetViewFactory(): void { + if (!Di::isRegistered(ViewFactory::class)) { + Di::register(ViewFactory::class); + } + $factory = Di::get(ViewFactory::class); $this->setPrivateProperty($factory, 'instance', null); } diff --git a/tests/_root/.env.testing b/tests/_root/.env.testing new file mode 100644 index 00000000..cdb4d2e0 --- /dev/null +++ b/tests/_root/.env.testing @@ -0,0 +1,2 @@ +APP_KEY=XYZ1234567890ABCDEFG123456789HIGKLMNOPQRSTUVWXYZ0123456789abcdefgh +DEBUG=TRUE \ No newline at end of file From c355805624002eb003199bb0948f9eadb581a176 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:06:47 +0400 Subject: [PATCH 67/77] [#373] Extract InitDebuggerStage into boot pipeline Made-with: Cursor --- src/App/Adapters/WebAppAdapter.php | 8 ++---- src/App/Stages/InitDebuggerStage.php | 43 ++++++++++++++++++++++++++++ src/App/Traits/WebAppTrait.php | 14 --------- 3 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 src/App/Stages/InitDebuggerStage.php diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 7c5fd805..e5cebd37 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -31,6 +31,7 @@ use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Exceptions\BaseException; use Quantum\App\Stages\LoadHelpersStage; +use Quantum\App\Stages\InitDebuggerStage; use Quantum\App\Stages\InitHttpStage; use Quantum\Di\Exceptions\DiException; use Quantum\App\Traits\WebAppTrait; @@ -63,6 +64,7 @@ public function __construct(AppContext $context) new LoadAppConfigStage(), new SetupErrorHandlerStage(), new InitHttpStage(), + new InitDebuggerStage(), ]); $pipeline->run($this->context); @@ -79,8 +81,6 @@ public function start(): ?int stop(); } - $this->initializeDebugger(); - if (!Di::isRegistered(ModuleLoader::class)) { Di::register(ModuleLoader::class); } @@ -109,10 +109,6 @@ public function start(): ?int (new LoadLanguageStage())->process($this->context); - if (!Di::isRegistered(Debugger::class)) { - Di::register(Debugger::class); - } - $debugger = Di::get(Debugger::class); if ($debugger->isEnabled()) { $debugger->addToStoreCell(Debugger::HOOKS, 'info', hook()->getRegistered()); diff --git a/src/App/Stages/InitDebuggerStage.php b/src/App/Stages/InitDebuggerStage.php new file mode 100644 index 00000000..b6ef6aec --- /dev/null +++ b/src/App/Stages/InitDebuggerStage.php @@ -0,0 +1,43 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Di\Exceptions\DiException; +use Quantum\Debugger\Debugger; +use Quantum\App\AppContext; +use ReflectionException; +use Quantum\Di\Di; + +/** + * Class InitDebuggerStage + * @package Quantum\App + */ +class InitDebuggerStage implements BootStageInterface +{ + /** + * @throws DiException|ReflectionException + */ + public function process(AppContext $context): void + { + if (!Di::isRegistered(Debugger::class)) { + Di::register(Debugger::class); + } + + Di::get(Debugger::class)->initStore(); + } +} diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index d74fa5a9..f87e3ca4 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -20,7 +20,6 @@ use Quantum\Loader\Exceptions\LoaderException; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; -use Quantum\Debugger\Debugger; use Quantum\Http\Response; use Quantum\Loader\Setup; use ReflectionException; @@ -32,19 +31,6 @@ */ trait WebAppTrait { - /** - * @throws DiException|ReflectionException - */ - private function initializeDebugger(): void - { - if (!Di::isRegistered(Debugger::class)) { - Di::register(Debugger::class); - } - - $debugger = Di::get(Debugger::class); - $debugger->initStore(); - } - /** * @throws ConfigException * @throws DiException From d5975678661b039d2eabf8a9875a4560289a830b Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:16:32 +0400 Subject: [PATCH 68/77] [#373] Extract LoadModulesStage for module loading and route building Made-with: Cursor --- src/App/Adapters/WebAppAdapter.php | 20 ++--------- src/App/Stages/LoadModulesStage.php | 56 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 src/App/Stages/LoadModulesStage.php diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index e5cebd37..2a9ad92d 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -29,6 +29,7 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\Middleware\MiddlewareManager; use Quantum\App\Stages\LoadLanguageStage; +use Quantum\App\Stages\LoadModulesStage; use Quantum\App\Exceptions\BaseException; use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Stages\InitDebuggerStage; @@ -37,8 +38,6 @@ use Quantum\App\Traits\WebAppTrait; use Quantum\Router\RouteCollection; use Quantum\Router\RouteDispatcher; -use Quantum\Router\RouteBuilder; -use Quantum\Module\ModuleLoader; use Quantum\Router\RouteFinder; use Quantum\Debugger\Debugger; use Quantum\App\BootPipeline; @@ -81,22 +80,9 @@ public function start(): ?int stop(); } - if (!Di::isRegistered(ModuleLoader::class)) { - Di::register(ModuleLoader::class); - } - - $moduleLoader = Di::get(ModuleLoader::class); - - $builder = new RouteBuilder(); - - $collection = $builder->build( - $moduleLoader->loadModulesRoutes(), - $moduleLoader->getModuleConfigs() - ); - - Di::set(RouteCollection::class, $collection); + (new LoadModulesStage())->process($this->context); - $routeFinder = new RouteFinder($collection); + $routeFinder = new RouteFinder(Di::get(RouteCollection::class)); $matchedRoute = $routeFinder->find(request()); diff --git a/src/App/Stages/LoadModulesStage.php b/src/App/Stages/LoadModulesStage.php new file mode 100644 index 00000000..a7ee94d7 --- /dev/null +++ b/src/App/Stages/LoadModulesStage.php @@ -0,0 +1,56 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 3.0.0 + */ + +namespace Quantum\App\Stages; + +use Quantum\Module\Exceptions\ModuleException; +use Quantum\Router\Exceptions\RouteException; +use Quantum\App\Contracts\BootStageInterface; +use Quantum\Di\Exceptions\DiException; +use Quantum\Router\RouteCollection; +use Quantum\Router\RouteBuilder; +use Quantum\Module\ModuleLoader; +use Quantum\App\AppContext; +use ReflectionException; +use Quantum\Di\Di; + +/** + * Class LoadModulesStage + * @package Quantum\App + */ +class LoadModulesStage implements BootStageInterface +{ + /** + * @throws ModuleException|RouteException|DiException|ReflectionException + */ + public function process(AppContext $context): void + { + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + + $moduleLoader = Di::get(ModuleLoader::class); + + $builder = new RouteBuilder(); + + $collection = $builder->build( + $moduleLoader->loadModulesRoutes(), + $moduleLoader->getModuleConfigs() + ); + + Di::set(RouteCollection::class, $collection); + } +} From cb7f982ea87944e696425845d733bbe3d2a7ec7d Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:52:59 +0400 Subject: [PATCH 69/77] [#373] Refactor WebAppAdapter start() into focused private methods in WebAppTrait, remove unused LoadLanguageStage and LoadModulesStage Made-with: Cursor --- src/App/Adapters/ConsoleAppAdapter.php | 3 - src/App/Adapters/WebAppAdapter.php | 44 +++------ src/App/Stages/LoadLanguageStage.php | 45 ---------- src/App/Stages/LoadModulesStage.php | 56 ------------ src/App/Traits/WebAppTrait.php | 89 ++++++++++++++++++- .../Unit/App/Stages/LoadLanguageStageTest.php | 37 -------- 6 files changed, 96 insertions(+), 178 deletions(-) delete mode 100644 src/App/Stages/LoadLanguageStage.php delete mode 100644 src/App/Stages/LoadModulesStage.php delete mode 100644 tests/Unit/App/Stages/LoadLanguageStageTest.php diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index ca9005ca..01c08082 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -23,7 +23,6 @@ use Quantum\App\Stages\LoadEnvironmentStage; use Symfony\Component\Console\Application; use Quantum\App\Stages\LoadAppConfigStage; -use Quantum\App\Stages\LoadLanguageStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Traits\ConsoleAppTrait; use Quantum\App\BootPipeline; @@ -82,8 +81,6 @@ public function __construct(AppContext $context) public function start(): ?int { try { - (new LoadLanguageStage())->process($this->context); - $this->registerCoreCommands(); $this->registerAppCommands(); diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 2a9ad92d..a8c54af2 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -27,23 +27,17 @@ use Quantum\Csrf\Exceptions\CsrfException; use Quantum\Lang\Exceptions\LangException; use Quantum\App\Stages\LoadAppConfigStage; -use Quantum\Middleware\MiddlewareManager; -use Quantum\App\Stages\LoadLanguageStage; -use Quantum\App\Stages\LoadModulesStage; use Quantum\App\Exceptions\BaseException; -use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Stages\InitDebuggerStage; +use Quantum\Middleware\MiddlewareManager; +use Quantum\App\Stages\LoadHelpersStage; use Quantum\App\Stages\InitHttpStage; use Quantum\Di\Exceptions\DiException; use Quantum\App\Traits\WebAppTrait; -use Quantum\Router\RouteCollection; use Quantum\Router\RouteDispatcher; -use Quantum\Router\RouteFinder; -use Quantum\Debugger\Debugger; use Quantum\App\BootPipeline; use Quantum\App\AppContext; use ReflectionException; -use Quantum\Di\Di; /** * Class WebAppAdapter @@ -80,42 +74,24 @@ public function start(): ?int stop(); } - (new LoadModulesStage())->process($this->context); - - $routeFinder = new RouteFinder(Di::get(RouteCollection::class)); - - $matchedRoute = $routeFinder->find(request()); - - if ($matchedRoute === null) { - page_not_found(); - stop(); - } - - request()->setMatchedRoute($matchedRoute); - - (new LoadLanguageStage())->process($this->context); + $this->loadModules(); - $debugger = Di::get(Debugger::class); - if ($debugger->isEnabled()) { - $debugger->addToStoreCell(Debugger::HOOKS, 'info', hook()->getRegistered()); - } + $matchedRoute = $this->resolveRoute(); - $middlewareManager = new MiddlewareManager($matchedRoute); + $this->loadLanguage(); - [$request, $response] = $middlewareManager->applyMiddlewares(request(), response()); + $this->logDebugInfo(); - $viewCache = $this->setupViewCache(); + [$request, $response] = (new MiddlewareManager($matchedRoute))->applyMiddlewares(request(), response()); - if ($viewCache->serveCachedView(route_uri() ?? '', $response)) { + if ($this->setupViewCache()->serveCachedView(route_uri() ?? '', $response)) { stop(); } - $dispatcher = new RouteDispatcher(); - $dispatcher->dispatch($matchedRoute, $request); + (new RouteDispatcher())->dispatch($matchedRoute, $request); stop(); } catch (StopExecutionException $exception) { - $this->handleCors(response()); - response()->send(); + $this->sendResponse(); return $exception->getCode(); } diff --git a/src/App/Stages/LoadLanguageStage.php b/src/App/Stages/LoadLanguageStage.php deleted file mode 100644 index 430d2b8e..00000000 --- a/src/App/Stages/LoadLanguageStage.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) - * @link http://quantum.softberg.org/ - * @since 3.0.0 - */ - -namespace Quantum\App\Stages; - -use Quantum\Config\Exceptions\ConfigException; -use Quantum\App\Contracts\BootStageInterface; -use Quantum\Lang\Exceptions\LangException; -use Quantum\App\Exceptions\BaseException; -use Quantum\Lang\Factories\LangFactory; -use Quantum\Di\Exceptions\DiException; -use Quantum\App\AppContext; -use ReflectionException; - -/** - * Class LoadLanguageStage - * @package Quantum\App - */ -class LoadLanguageStage implements BootStageInterface -{ - /** - * @throws LangException|ConfigException|DiException|BaseException|ReflectionException - */ - public function process(AppContext $context): void - { - $lang = LangFactory::get(); - - if ($lang->isEnabled()) { - $lang->load(); - } - } -} diff --git a/src/App/Stages/LoadModulesStage.php b/src/App/Stages/LoadModulesStage.php deleted file mode 100644 index a7ee94d7..00000000 --- a/src/App/Stages/LoadModulesStage.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) - * @link http://quantum.softberg.org/ - * @since 3.0.0 - */ - -namespace Quantum\App\Stages; - -use Quantum\Module\Exceptions\ModuleException; -use Quantum\Router\Exceptions\RouteException; -use Quantum\App\Contracts\BootStageInterface; -use Quantum\Di\Exceptions\DiException; -use Quantum\Router\RouteCollection; -use Quantum\Router\RouteBuilder; -use Quantum\Module\ModuleLoader; -use Quantum\App\AppContext; -use ReflectionException; -use Quantum\Di\Di; - -/** - * Class LoadModulesStage - * @package Quantum\App - */ -class LoadModulesStage implements BootStageInterface -{ - /** - * @throws ModuleException|RouteException|DiException|ReflectionException - */ - public function process(AppContext $context): void - { - if (!Di::isRegistered(ModuleLoader::class)) { - Di::register(ModuleLoader::class); - } - - $moduleLoader = Di::get(ModuleLoader::class); - - $builder = new RouteBuilder(); - - $collection = $builder->build( - $moduleLoader->loadModulesRoutes(), - $moduleLoader->getModuleConfigs() - ); - - Di::set(RouteCollection::class, $collection); - } -} diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index f87e3ca4..60575903 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -16,14 +16,27 @@ namespace Quantum\App\Traits; +use Quantum\App\Exceptions\StopExecutionException; +use Quantum\Module\Exceptions\ModuleException; use Quantum\Config\Exceptions\ConfigException; use Quantum\Loader\Exceptions\LoaderException; +use Quantum\Router\Exceptions\RouteException; +use Quantum\Lang\Exceptions\LangException; +use Quantum\App\Exceptions\BaseException; +use Quantum\Lang\Factories\LangFactory; use Quantum\Di\Exceptions\DiException; use Quantum\ResourceCache\ViewCache; +use Quantum\Router\RouteCollection; +use Quantum\Router\RouteBuilder; +use Quantum\Module\ModuleLoader; +use Quantum\Router\MatchedRoute; +use Quantum\Router\RouteFinder; +use Quantum\Debugger\Debugger; use Quantum\Http\Response; use Quantum\Loader\Setup; use ReflectionException; use Quantum\Di\Di; +use Exception; /** * Trait WebAppTrait @@ -32,9 +45,70 @@ trait WebAppTrait { /** - * @throws ConfigException - * @throws DiException - * @throws ReflectionException|LoaderException + * @throws ModuleException|RouteException|DiException|ReflectionException + */ + private function loadModules(): void + { + if (!Di::isRegistered(ModuleLoader::class)) { + Di::register(ModuleLoader::class); + } + + $moduleLoader = Di::get(ModuleLoader::class); + + $collection = (new RouteBuilder())->build( + $moduleLoader->loadModulesRoutes(), + $moduleLoader->getModuleConfigs() + ); + + Di::set(RouteCollection::class, $collection); + } + + /** + * @return MatchedRoute + * @throws RouteException|StopExecutionException|ConfigException|BaseException|DiException|ReflectionException + */ + private function resolveRoute(): MatchedRoute + { + $routeFinder = new RouteFinder(Di::get(RouteCollection::class)); + + $matchedRoute = $routeFinder->find(request()); + + if ($matchedRoute === null) { + page_not_found(); + stop(); + } + + request()->setMatchedRoute($matchedRoute); + + return $matchedRoute; + } + + /** + * @throws LangException|ConfigException|DiException|BaseException|ReflectionException + */ + private function loadLanguage(): void + { + $lang = LangFactory::get(); + + if ($lang->isEnabled()) { + $lang->load(); + } + } + + /** + * @throws DiException|ReflectionException + */ + private function logDebugInfo(): void + { + $debugger = Di::get(Debugger::class); + + if ($debugger->isEnabled()) { + $debugger->addToStoreCell(Debugger::HOOKS, 'info', hook()->getRegistered()); + } + } + + /** + * @throws ConfigException|DiException|ReflectionException|LoaderException */ private function setupViewCache(): ViewCache { @@ -64,4 +138,13 @@ private function handleCors(Response $response): void $response->setHeader($key, (string) $value); } } + + /** + * @throws ConfigException|LoaderException|DiException|ReflectionException|Exception + */ + private function sendResponse(): void + { + $this->handleCors(response()); + response()->send(); + } } diff --git a/tests/Unit/App/Stages/LoadLanguageStageTest.php b/tests/Unit/App/Stages/LoadLanguageStageTest.php deleted file mode 100644 index fc71fd86..00000000 --- a/tests/Unit/App/Stages/LoadLanguageStageTest.php +++ /dev/null @@ -1,37 +0,0 @@ -context = $this->createContext(); - - (new LoadHelpersStage())->process($this->context); - (new LoadEnvironmentStage())->process($this->context); - (new LoadAppConfigStage())->process($this->context); - } - - public function tearDown(): void - { - config()->flush(); - Di::reset(); - } - - public function testLoadLanguageStageRunsWithoutError(): void - { - $stage = new LoadLanguageStage(); - $stage->process($this->context); - - $this->assertTrue(true); - } -} From e8749c260bc43dabdea3ebea974320154e42b627 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 13:59:25 +0400 Subject: [PATCH 70/77] [#373] Update CHANGELOG.md with app bootstrapping and DI ownership refactor Made-with: Cursor --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b663c9..c7b47dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ ## [3.0.0] - TBD ### Changed +- **BREAKING:** Refactored app bootstrapping and DI ownership model (#373): + - Introduced `AppContext` as the central execution state holder (mode, baseDir, DiContainer, Environment, Config, Request, Response, Routes) + - Split `Di` into a static facade delegating to an instance-based `DiContainer`, one container per application execution + - Added `Di::set()`, `Di::has()`, and `Di::isRegistered()` for explicit service management + - `Di::get()` now enforces a strict contract: throws if the dependency is not explicitly registered (no more implicit auto-registration) + - Introduced `BootPipeline` and `BootStageInterface` for explicit, ordered boot sequences + - Extracted boot stages: `LoadHelpersStage`, `LoadEnvironmentStage`, `LoadAppConfigStage`, `SetupErrorHandlerStage`, `InitHttpStage`, `InitDebuggerStage` + - Removed `AppTrait`; all adapter boot logic moved to pipeline stages and focused private methods in `WebAppTrait`/`ConsoleAppTrait` +- **BREAKING:** Converted `Request` and `Response` from static facades to instance-based classes (#454): + - Merged `HttpRequest`/`HttpResponse` wrapper layer directly into `Request`/`Response` + - All request/response access now goes through `request()` and `response()` helper functions +- **BREAKING:** Removed all static singleton patterns from core services (#381, #382): + - Migrated all 12 first-party factories (Auth, Archive, Cache, Captcha, Cryptor, FileSystem, Lang, Logger, Mailer, Renderer, Session, View) from static instance caches to DI-managed lifetimes + - Migrated service singletons to DI ownership: Cookie, Config, Environment, Server, AssetManager, Csrf, Database, MailTrap, Debugger, ViewCache, ErrorHandler, HookManager, ModuleLoader +- **BREAKING:** `Environment` class is no longer a static singleton (#456): + - Uses `Dotenv::createArrayBacked()` for isolated, deterministic env loading + - New `environment()` helper function and shorthand check methods: `isProduction()`, `isTesting()`, `isStaging()`, `isDevelopment()`, `isLocal()` + - `env()` helper now delegates through `environment()->getValue()` + - **BREAKING:** Minimum PHP version requirement raised from 7.3 to 7.4 - Modernized codebase with PHP 7.4+ syntax using Rector: - Array destructuring: `list()` → `[]` @@ -48,6 +67,11 @@ - Fixed cURL error message assertions for cross-version compatibility ### Added +- `AppContext` class representing the runtime identity of a single application execution +- `DiContainer` instance-based dependency injection container, isolated per execution +- `BootPipeline` and `BootStageInterface` for declarative, ordered boot sequences +- `environment()` global helper and `Environment` shorthand methods (`isProduction()`, `isTesting()`, etc.) +- Lazy registration guards (`Di::register()` + `Di::isRegistered()`) at all DI call sites for explicit dependency management - Rector as dev dependency for automated code refactoring - Additional PHP extensions required in CI: `bcmath`, `gd`, `zip` - PHPUnit strict testing flags: `--fail-on-warning`, `--fail-on-risky` @@ -75,3 +99,9 @@ ### Removed - Support for PHP 7.3 and earlier versions - Legacy routing static state and implicit controller resolution via `RouteController` +- `AppTrait` — replaced by boot pipeline stages and adapter-specific traits +- Static singleton patterns from all core services and factories +- `Environment::getInstance()` static singleton accessor +- `HttpRequest`/`HttpResponse` static facade wrapper classes (merged into `Request`/`Response`) +- Implicit auto-registration in `Di::get()` — all dependencies must be explicitly registered +- `RegisterCoreDependenciesStage` and `dependencies.php` — replaced by lazy registration at call sites From 56e3ce9eea446ef75e92c84d94c8a232ce016804 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:11:37 +0400 Subject: [PATCH 71/77] [#373] Fix PHP 8.x ReflectionProperty::setValue() for static properties, remove dead LangFactory instance reset Made-with: Cursor --- tests/Unit/AppTestCase.php | 7 ++++++- tests/Unit/Lang/Helpers/LangHelperFunctionsTest.php | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index a4674793..d16edade 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -65,7 +65,12 @@ protected function setPrivateProperty($object, $property, $value): void $reflection = new ReflectionClass($object); $property = $reflection->getProperty($property); $property->setAccessible(true); - $property->setValue($object, $value); + + if (is_string($object)) { + $property->setValue(null, $value); + } else { + $property->setValue($object, $value); + } } protected function getPrivateProperty($object, $property) diff --git a/tests/Unit/Lang/Helpers/LangHelperFunctionsTest.php b/tests/Unit/Lang/Helpers/LangHelperFunctionsTest.php index 46981484..7ad31bd2 100644 --- a/tests/Unit/Lang/Helpers/LangHelperFunctionsTest.php +++ b/tests/Unit/Lang/Helpers/LangHelperFunctionsTest.php @@ -14,8 +14,6 @@ public function setUp(): void { parent::setUp(); - $this->setPrivateProperty(LangFactory::class, 'instance', null); - $this->lang = LangFactory::get(); $this->lang->load(); From c641d74772b340b14dfcc4a68d180cfeaf3cfbf8 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:32:25 +0400 Subject: [PATCH 72/77] [#373] Remove redundant Di::get() calls from InitHttpStage Made-with: Cursor --- src/App/Stages/InitHttpStage.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/App/Stages/InitHttpStage.php b/src/App/Stages/InitHttpStage.php index f62bbf48..45956bf1 100644 --- a/src/App/Stages/InitHttpStage.php +++ b/src/App/Stages/InitHttpStage.php @@ -42,8 +42,5 @@ public function process(AppContext $context): void if (!Di::isRegistered(Response::class)) { Di::register(Response::class); } - - Di::get(Request::class); - Di::get(Response::class); } } From 261977b51cdc3f8e53b86c72de6d4a27dd799300 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:43:36 +0400 Subject: [PATCH 73/77] [#373] Make AppContext the single owner of DiContainer, Di becomes a pure facade Made-with: Cursor --- src/App/AppContext.php | 11 ++---- src/App/Factories/AppFactory.php | 2 - src/Di/Di.php | 35 +++-------------- .../App/Adapters/ConsoleAppAdapterTest.php | 3 +- tests/Unit/App/Adapters/WebAppAdapterTest.php | 3 +- tests/Unit/App/AppContextTest.php | 33 ++-------------- tests/Unit/App/AppTest.php | 8 +--- tests/Unit/App/BootPipelineTest.php | 9 +++-- .../App/Stages/LoadAppConfigStageTest.php | 4 +- .../App/Stages/LoadEnvironmentStageTest.php | 5 +-- .../Unit/App/Stages/LoadHelpersStageTest.php | 4 +- .../App/Stages/SetupErrorHandlerStageTest.php | 4 +- tests/Unit/AppTestCase.php | 16 +++++--- tests/Unit/Di/DiTest.php | 39 ++++--------------- tests/Unit/Mailer/MailerTestCase.php | 4 +- tests/Unit/Module/ModuleManagerTest.php | 4 +- 16 files changed, 47 insertions(+), 137 deletions(-) diff --git a/src/App/AppContext.php b/src/App/AppContext.php index 1222df8e..3feabe31 100644 --- a/src/App/AppContext.php +++ b/src/App/AppContext.php @@ -24,7 +24,6 @@ use Quantum\Config\Config; use Quantum\Http\Response; use Quantum\Http\Request; -use RuntimeException; /** * Class AppContext @@ -36,9 +35,9 @@ class AppContext private string $baseDir; - private ?DiContainer $container; + private DiContainer $container; - public function __construct(string $mode, string $baseDir = '', ?DiContainer $container = null) + public function __construct(string $mode, string $baseDir, DiContainer $container) { if (!in_array($mode, [AppType::WEB, AppType::CONSOLE], true)) { throw new InvalidArgumentException("Invalid app mode: $mode"); @@ -59,7 +58,7 @@ public function getBaseDir(): string return $this->baseDir; } - public function getContainer(): ?DiContainer + public function getContainer(): DiContainer { return $this->container; } @@ -106,10 +105,6 @@ public function getRoutes(): RouteCollection */ private function resolveFromContainer(string $class) { - if ($this->container === null) { - throw new RuntimeException('DiContainer is not set on AppContext.'); - } - return $this->container->get($class); } } diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index 4d756e09..d7c6a971 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -24,7 +24,6 @@ use Quantum\App\AppContext; use Quantum\Di\DiContainer; use Quantum\App\App; -use Quantum\Di\Di; /** * Class AppFactory @@ -73,7 +72,6 @@ private static function createInstance(string $type, string $baseDir): App } $container = new DiContainer(); - Di::setCurrent($container); $context = new AppContext($type, $baseDir, $container); App::setContext($context); diff --git a/src/Di/Di.php b/src/Di/Di.php index 24cde4bd..bb2aab9f 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -17,13 +17,15 @@ namespace Quantum\Di; use Quantum\Di\Exceptions\DiException; +use Quantum\App\App; use ReflectionException; /** * Di Class * - * Static facade that delegates all calls to the current DiContainer instance. - * Preserves the existing static API for full backward compatibility. + * Static facade that delegates all calls to the DiContainer + * owned by AppContext. Preserves the existing static API + * for full backward compatibility. * * @package Quantum/Di * @method static void registerDependencies(array $dependencies) @@ -39,36 +41,11 @@ class Di { /** - * @var DiContainer|null - */ - private static ?DiContainer $current = null; - - /** - * Sets the current container instance - */ - public static function setCurrent(DiContainer $container): void - { - self::$current = $container; - } - - /** - * Gets the current container instance, lazily creating one if needed + * Gets the current container instance from AppContext */ public static function getCurrent(): DiContainer { - if (self::$current === null) { - self::$current = new DiContainer(); - } - - return self::$current; - } - - /** - * Resets the current container by replacing it with a fresh instance - */ - public static function reset(): void - { - self::$current = new DiContainer(); + return App::getContext()->getContainer(); } /** diff --git a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php index 57cec7d2..989b96f3 100644 --- a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php +++ b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php @@ -6,7 +6,6 @@ use Symfony\Component\Console\Application; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; -use Quantum\Di\Di; use Exception; use Mockery; @@ -32,7 +31,7 @@ public function setUp(): void public function tearDown(): void { config()->flush(); - Di::reset(); + $this->clearAppContext(); } public function testConsoleAppAdapterStartSuccessfully(): void diff --git a/tests/Unit/App/Adapters/WebAppAdapterTest.php b/tests/Unit/App/Adapters/WebAppAdapterTest.php index 23135d9e..95b8e7fd 100644 --- a/tests/Unit/App/Adapters/WebAppAdapterTest.php +++ b/tests/Unit/App/Adapters/WebAppAdapterTest.php @@ -4,7 +4,6 @@ use Quantum\App\Adapters\WebAppAdapter; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Di\Di; class WebAppAdapterTest extends AppTestCase { @@ -18,7 +17,7 @@ public function setUp(): void public function tearDown(): void { config()->flush(); - Di::reset(); + $this->clearAppContext(); } public function testWebAppAdapterStartSuccessfully(): void diff --git a/tests/Unit/App/AppContextTest.php b/tests/Unit/App/AppContextTest.php index eeabb463..b6e8ffd5 100644 --- a/tests/Unit/App/AppContextTest.php +++ b/tests/Unit/App/AppContextTest.php @@ -12,14 +12,13 @@ use Quantum\Config\Config; use Quantum\Http\Response; use Quantum\Http\Request; -use RuntimeException; use Mockery; class AppContextTest extends TestCase { public function testAppContextWebMode(): void { - $context = new AppContext(AppType::WEB); + $context = new AppContext(AppType::WEB, '', new DiContainer()); $this->assertSame(AppType::WEB, $context->getMode()); $this->assertTrue($context->isWebMode()); @@ -28,7 +27,7 @@ public function testAppContextWebMode(): void public function testAppContextConsoleMode(): void { - $context = new AppContext(AppType::CONSOLE); + $context = new AppContext(AppType::CONSOLE, '', new DiContainer()); $this->assertSame(AppType::CONSOLE, $context->getMode()); $this->assertFalse($context->isWebMode()); @@ -40,23 +39,16 @@ public function testAppContextRejectsInvalidMode(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid app mode: invalid'); - new AppContext('invalid'); + new AppContext('invalid', '', new DiContainer()); } public function testAppContextBaseDir(): void { - $context = new AppContext(AppType::WEB, '/my/base/dir'); + $context = new AppContext(AppType::WEB, '/my/base/dir', new DiContainer()); $this->assertSame('/my/base/dir', $context->getBaseDir()); } - public function testAppContextBaseDirDefaultsToEmpty(): void - { - $context = new AppContext(AppType::WEB); - - $this->assertSame('', $context->getBaseDir()); - } - public function testAppContextContainer(): void { $container = new DiContainer(); @@ -65,13 +57,6 @@ public function testAppContextContainer(): void $this->assertSame($container, $context->getContainer()); } - public function testAppContextContainerDefaultsToNull(): void - { - $context = new AppContext(AppType::WEB); - - $this->assertNull($context->getContainer()); - } - public function testAppContextGetEnvironment(): void { $container = new DiContainer(); @@ -126,14 +111,4 @@ public function testAppContextGetRoutes(): void $this->assertSame($routes, $context->getRoutes()); } - - public function testAppContextAccessorThrowsWhenContainerIsNull(): void - { - $context = new AppContext(AppType::WEB); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('DiContainer is not set on AppContext.'); - - $context->getEnvironment(); - } } diff --git a/tests/Unit/App/AppTest.php b/tests/Unit/App/AppTest.php index 50fa979e..3bd31d76 100644 --- a/tests/Unit/App/AppTest.php +++ b/tests/Unit/App/AppTest.php @@ -9,7 +9,6 @@ use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; use Quantum\App\App; -use Quantum\Di\Di; /** * @runInSeparateProcess @@ -19,15 +18,13 @@ class AppTest extends AppTestCase { public function setUp(): void { - parent::setUp(); - - Di::reset(); + $this->createContext(); } public function tearDown(): void { config()->flush(); - Di::reset(); + $this->clearAppContext(); } public function testAppGetAdapter(): void @@ -39,7 +36,6 @@ public function testAppGetAdapter(): void $this->assertInstanceOf(AppInterface::class, $app->getAdapter()); config()->flush(); - Di::reset(); $app = new App(new ConsoleAppAdapter($this->createContext(AppType::CONSOLE))); diff --git a/tests/Unit/App/BootPipelineTest.php b/tests/Unit/App/BootPipelineTest.php index 2d66bdad..be8b9fcc 100644 --- a/tests/Unit/App/BootPipelineTest.php +++ b/tests/Unit/App/BootPipelineTest.php @@ -6,6 +6,7 @@ use Quantum\App\BootPipeline; use Quantum\App\AppContext; use Quantum\App\Enums\AppType; +use Quantum\Di\DiContainer; use PHPUnit\Framework\TestCase; use InvalidArgumentException; use RuntimeException; @@ -29,7 +30,7 @@ public function testPipelineRunsStagesInOrder(): void }); $pipeline = new BootPipeline([$stage1, $stage2, $stage3]); - $pipeline->run(new AppContext(AppType::WEB)); + $pipeline->run(new AppContext(AppType::WEB, '', new DiContainer())); $this->assertSame(['first', 'second', 'third'], $log); } @@ -37,7 +38,7 @@ public function testPipelineRunsStagesInOrder(): void public function testEmptyPipelineRunsWithoutError(): void { $pipeline = new BootPipeline([]); - $pipeline->run(new AppContext(AppType::WEB)); + $pipeline->run(new AppContext(AppType::WEB, '', new DiContainer())); $this->assertTrue(true); } @@ -51,7 +52,7 @@ public function testPipelinePassesContextToStages(): void }); $pipeline = new BootPipeline([$stage]); - $pipeline->run(new AppContext(AppType::CONSOLE)); + $pipeline->run(new AppContext(AppType::CONSOLE, '', new DiContainer())); $this->assertSame(AppType::CONSOLE, $receivedMode); } @@ -66,7 +67,7 @@ public function testPipelinePropagatesException(): void $this->expectExceptionMessage('Stage failed'); $pipeline = new BootPipeline([$stage]); - $pipeline->run(new AppContext(AppType::WEB)); + $pipeline->run(new AppContext(AppType::WEB, '', new DiContainer())); } public function testPipelineRejectsInvalidStage(): void diff --git a/tests/Unit/App/Stages/LoadAppConfigStageTest.php b/tests/Unit/App/Stages/LoadAppConfigStageTest.php index a519727a..e9deebba 100644 --- a/tests/Unit/App/Stages/LoadAppConfigStageTest.php +++ b/tests/Unit/App/Stages/LoadAppConfigStageTest.php @@ -6,13 +6,11 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Di\Di; class LoadAppConfigStageTest extends AppTestCase { public function setUp(): void { - Di::reset(); $this->context = $this->createContext(); (new LoadHelpersStage())->process($this->context); @@ -22,7 +20,7 @@ public function setUp(): void public function tearDown(): void { config()->flush(); - Di::reset(); + $this->clearAppContext(); } public function testLoadAppConfigStageImportsAppConfig(): void diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php index 2d019587..5c1365f0 100644 --- a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -6,13 +6,11 @@ use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; use Quantum\App\Enums\AppType; -use Quantum\Di\Di; class LoadEnvironmentStageTest extends AppTestCase { public function setUp(): void { - Di::reset(); $this->context = $this->createContext(); (new LoadHelpersStage())->process($this->context); @@ -20,7 +18,7 @@ public function setUp(): void public function tearDown(): void { - Di::reset(); + $this->clearAppContext(); } public function testLoadEnvironmentStageLoadsEnvVars(): void @@ -33,7 +31,6 @@ public function testLoadEnvironmentStageLoadsEnvVars(): void public function testLoadEnvironmentStageSetsMutableForConsole(): void { - Di::reset(); $consoleContext = $this->createContext(AppType::CONSOLE); (new LoadHelpersStage())->process($consoleContext); diff --git a/tests/Unit/App/Stages/LoadHelpersStageTest.php b/tests/Unit/App/Stages/LoadHelpersStageTest.php index 4b12941d..dc924463 100644 --- a/tests/Unit/App/Stages/LoadHelpersStageTest.php +++ b/tests/Unit/App/Stages/LoadHelpersStageTest.php @@ -4,19 +4,17 @@ use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Di\Di; class LoadHelpersStageTest extends AppTestCase { public function setUp(): void { - Di::reset(); $this->context = $this->createContext(); } public function tearDown(): void { - Di::reset(); + $this->clearAppContext(); } public function testLoadHelpersStageLoadsComponentHelpers(): void diff --git a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php index cf117fc4..d5049c02 100644 --- a/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php +++ b/tests/Unit/App/Stages/SetupErrorHandlerStageTest.php @@ -7,13 +7,11 @@ use Quantum\App\Stages\LoadAppConfigStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\Di\Di; class SetupErrorHandlerStageTest extends AppTestCase { public function setUp(): void { - Di::reset(); $this->context = $this->createContext(); (new LoadHelpersStage())->process($this->context); @@ -26,7 +24,7 @@ public function tearDown(): void restore_error_handler(); restore_exception_handler(); config()->flush(); - Di::reset(); + $this->clearAppContext(); } public function testSetupErrorHandlerStageRegistersHandlers(): void diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index d16edade..30a9cde7 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -16,6 +16,7 @@ use ReflectionClass; use Quantum\App\App; use Quantum\Di\Di; +use ReflectionProperty; abstract class AppTestCase extends TestCase { @@ -46,20 +47,25 @@ public function tearDown(): void if (Di::isRegistered(Debugger::class)) { Di::get(Debugger::class)->resetStore(); } - Di::reset(); + + $this->clearAppContext(); } protected function createContext(string $mode = AppType::WEB): AppContext { - $container = new DiContainer(); - Di::setCurrent($container); - - $context = new AppContext($mode, PROJECT_ROOT, $container); + $context = new AppContext($mode, PROJECT_ROOT, new DiContainer()); App::setContext($context); return $context; } + protected function clearAppContext(): void + { + $prop = new ReflectionProperty(App::class, 'context'); + $prop->setAccessible(true); + $prop->setValue(null, null); + } + protected function setPrivateProperty($object, $property, $value): void { $reflection = new ReflectionClass($object); diff --git a/tests/Unit/Di/DiTest.php b/tests/Unit/Di/DiTest.php index a0037153..477bb2ed 100644 --- a/tests/Unit/Di/DiTest.php +++ b/tests/Unit/Di/DiTest.php @@ -51,11 +51,10 @@ public function __construct(CircularDependencyA $a) use Quantum\Di\Exceptions\DiException; use Quantum\Tests\Unit\AppTestCase; use Quantum\Service\DummyService; - use Quantum\Di\DiContainer; use Quantum\Http\Response; use Quantum\Http\Request; use Quantum\Loader\Setup; - use ReflectionProperty; + use Quantum\App\App; use Quantum\Di\Di; class DiTest extends AppTestCase @@ -65,10 +64,9 @@ public function setUp(): void parent::setUp(); } - public function testFacadeDelegatesToCurrentContainer(): void + public function testFacadeDelegatesToAppContextContainer(): void { - $container = new DiContainer(); - Di::setCurrent($container); + $container = App::getContext()->getContainer(); Di::register(DummyService::class); @@ -80,37 +78,14 @@ public function testFacadeDelegatesToCurrentContainer(): void $this->assertSame($instance, $container->get(DummyService::class)); } - public function testSetAndGetCurrent(): void + public function testGetCurrentReturnsAppContextContainer(): void { - $container = new DiContainer(); - Di::setCurrent($container); + $container = App::getContext()->getContainer(); $this->assertSame($container, Di::getCurrent()); } - public function testGetCurrentCreatesDefaultContainer(): void - { - $currentProp = new ReflectionProperty(Di::class, 'current'); - $currentProp->setAccessible(true); - $currentProp->setValue(null, null); - - $container = Di::getCurrent(); - - $this->assertInstanceOf(DiContainer::class, $container); - } - - public function testResetCreatesNewContainer(): void - { - $containerBefore = Di::getCurrent(); - - Di::reset(); - - $containerAfter = Di::getCurrent(); - - $this->assertNotSame($containerBefore, $containerAfter); - } - - public function testResetClearsRegistrationsAndInstances(): void + public function testNewContextGivesCleanContainer(): void { Di::register(DummyService::class); Di::get(DummyService::class); @@ -118,7 +93,7 @@ public function testResetClearsRegistrationsAndInstances(): void $this->assertTrue(Di::isRegistered(DummyService::class)); $this->assertTrue(Di::has(DummyService::class)); - Di::reset(); + $this->createContext(); $this->assertFalse(Di::isRegistered(DummyService::class)); $this->assertFalse(Di::has(DummyService::class)); diff --git a/tests/Unit/Mailer/MailerTestCase.php b/tests/Unit/Mailer/MailerTestCase.php index 91ac5b43..c40604e5 100644 --- a/tests/Unit/Mailer/MailerTestCase.php +++ b/tests/Unit/Mailer/MailerTestCase.php @@ -10,8 +10,6 @@ abstract class MailerTestCase extends AppTestCase protected $adapter; public function tearDown(): void { - parent::tearDown(); - $coreDependencies = [ \Quantum\Loader\Loader::class => \Quantum\Loader\Loader::class, \Quantum\Http\Request::class => \Quantum\Http\Request::class, @@ -26,6 +24,6 @@ public function tearDown(): void $this->fs->remove($emailFile); } - Di::reset(); + parent::tearDown(); } } diff --git a/tests/Unit/Module/ModuleManagerTest.php b/tests/Unit/Module/ModuleManagerTest.php index 84c4d132..cf178555 100644 --- a/tests/Unit/Module/ModuleManagerTest.php +++ b/tests/Unit/Module/ModuleManagerTest.php @@ -85,8 +85,6 @@ public function testInvalidTemplate(): void public function tearDown(): void { - parent::tearDown(); - $moduleConfigs = $this->fs->require($this->modulesConfigPath); $apiModulePath = App::getBaseDir() . DS . 'modules' . DS . 'Api'; @@ -103,5 +101,7 @@ public function tearDown(): void if ($this->fs->isDirectory($apiModulePath)) { deleteDirectoryWithFiles($apiModulePath); } + + parent::tearDown(); } } From a7c960a8f1bde567456618c75a4f51ecf5253a85 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:02:23 +0400 Subject: [PATCH 74/77] [#373] Extract loadConfig() in Config to remove Loader registration duplication Made-with: Cursor --- src/Config/Config.php | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Config/Config.php b/src/Config/Config.php index 9e9862e0..bf008d7f 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -44,11 +44,7 @@ public function load(Setup $setup): void return; } - if (!Di::isRegistered(Loader::class)) { - Di::register(Loader::class); - } - - $this->configs = new Data(Di::get(Loader::class)->setup($setup)->load()); + $this->configs = new Data($this->loadConfig($setup)); } /** @@ -65,14 +61,12 @@ public function import(Setup $setup): void throw ConfigException::configCollision($fileName); } - if (!Di::isRegistered(Loader::class)) { - Di::register(Loader::class); - } + $data = $this->loadConfig($setup); if (!$this->configs) { - $this->configs = new Data([$fileName => Di::get(Loader::class)->setup($setup)->load()]); + $this->configs = new Data([$fileName => $data]); } else { - $this->configs->import([$fileName => Di::get(Loader::class)->setup($setup)->load()]); + $this->configs->import([$fileName => $data]); } } @@ -131,4 +125,17 @@ public function flush(): void { $this->configs = null; } + + /** + * @return array + * @throws DiException|LoaderException|ReflectionException + */ + private function loadConfig(Setup $setup): array + { + if (!Di::isRegistered(Loader::class)) { + Di::register(Loader::class); + } + + return Di::get(Loader::class)->setup($setup)->load(); + } } From 62c7c79a19d60a2d50d9e2c41e92f38ced5dbaf7 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:32:16 +0400 Subject: [PATCH 75/77] [#373] Remove redundant Debugger registration guard from ViewFactory Made-with: Cursor --- src/View/Factories/ViewFactory.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/View/Factories/ViewFactory.php b/src/View/Factories/ViewFactory.php index 059f1eed..f1d3d652 100644 --- a/src/View/Factories/ViewFactory.php +++ b/src/View/Factories/ViewFactory.php @@ -53,10 +53,6 @@ public static function get(): QtView public function resolve(): QtView { if ($this->instance === null) { - if (!Di::isRegistered(Debugger::class)) { - Di::register(Debugger::class); - } - if (!Di::isRegistered(ViewCache::class)) { Di::register(ViewCache::class); } From b1a2fa075879044deab71ec37113e5b881020054 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:01:04 +0400 Subject: [PATCH 76/77] [#373] Remove mode from AppContext, move console-specific behavior to ConsoleAppAdapter Made-with: Cursor --- src/App/Adapters/ConsoleAppAdapter.php | 4 ++ src/App/AppContext.php | 26 +----------- src/App/Factories/AppFactory.php | 2 +- src/App/Stages/LoadEnvironmentStage.php | 4 -- .../App/Adapters/ConsoleAppAdapterTest.php | 5 +-- tests/Unit/App/AppContextTest.php | 42 ++++--------------- tests/Unit/App/AppTest.php | 3 +- tests/Unit/App/BootPipelineTest.php | 17 ++++---- .../App/Stages/LoadEnvironmentStageTest.php | 13 ------ tests/Unit/AppTestCase.php | 4 +- 10 files changed, 26 insertions(+), 94 deletions(-) diff --git a/src/App/Adapters/ConsoleAppAdapter.php b/src/App/Adapters/ConsoleAppAdapter.php index 01c08082..1854becd 100644 --- a/src/App/Adapters/ConsoleAppAdapter.php +++ b/src/App/Adapters/ConsoleAppAdapter.php @@ -69,6 +69,10 @@ public function __construct(AppContext $context) $pipeline = new BootPipeline($stages); $pipeline->run($this->context); + if ($commandName !== 'core:env') { + environment()->setMutable(true); + } + $this->application = $this->createApplication( config()->get('app.name', 'UNKNOWN'), config()->get('app.version', 'UNKNOWN') diff --git a/src/App/AppContext.php b/src/App/AppContext.php index 3feabe31..a9d9d06d 100644 --- a/src/App/AppContext.php +++ b/src/App/AppContext.php @@ -18,8 +18,6 @@ use Quantum\Environment\Environment; use Quantum\Router\RouteCollection; -use Quantum\App\Enums\AppType; -use InvalidArgumentException; use Quantum\Di\DiContainer; use Quantum\Config\Config; use Quantum\Http\Response; @@ -31,28 +29,16 @@ */ class AppContext { - private string $mode; - private string $baseDir; private DiContainer $container; - public function __construct(string $mode, string $baseDir, DiContainer $container) + public function __construct(string $baseDir, DiContainer $container) { - if (!in_array($mode, [AppType::WEB, AppType::CONSOLE], true)) { - throw new InvalidArgumentException("Invalid app mode: $mode"); - } - - $this->mode = $mode; $this->baseDir = $baseDir; $this->container = $container; } - public function getMode(): string - { - return $this->mode; - } - public function getBaseDir(): string { return $this->baseDir; @@ -63,16 +49,6 @@ public function getContainer(): DiContainer return $this->container; } - public function isWebMode(): bool - { - return $this->mode === AppType::WEB; - } - - public function isConsoleMode(): bool - { - return $this->mode === AppType::CONSOLE; - } - public function getEnvironment(): Environment { return $this->resolveFromContainer(Environment::class); diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index d7c6a971..e00a1bd3 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -73,7 +73,7 @@ private static function createInstance(string $type, string $baseDir): App $container = new DiContainer(); - $context = new AppContext($type, $baseDir, $container); + $context = new AppContext($baseDir, $container); App::setContext($context); $adapterClass = self::ADAPTERS[$type]; diff --git a/src/App/Stages/LoadEnvironmentStage.php b/src/App/Stages/LoadEnvironmentStage.php index cf3a48eb..7aaa2dab 100644 --- a/src/App/Stages/LoadEnvironmentStage.php +++ b/src/App/Stages/LoadEnvironmentStage.php @@ -39,10 +39,6 @@ public function process(AppContext $context): void { $environment = new Environment(); - if ($context->isConsoleMode()) { - $environment->setMutable(true); - } - $environment->load(new Setup('config', 'env')); Di::set(Environment::class, $environment); diff --git a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php index 989b96f3..21911c9a 100644 --- a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php +++ b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php @@ -5,7 +5,6 @@ use Quantum\App\Adapters\ConsoleAppAdapter; use Symfony\Component\Console\Application; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\Enums\AppType; use Exception; use Mockery; @@ -38,7 +37,7 @@ public function testConsoleAppAdapterStartSuccessfully(): void { $_SERVER['argv'] = ['qt', 'list', '--quiet']; - $this->consoleAppAdapter->__construct($this->createContext(AppType::CONSOLE)); + $this->consoleAppAdapter->__construct($this->createContext()); $result = $this->consoleAppAdapter->start(); @@ -49,7 +48,7 @@ public function testConsoleAppAdapterStartFails(): void { $_SERVER['argv'] = ['qt', 'unknown', '--quiet']; - $this->consoleAppAdapter->__construct($this->createContext(AppType::CONSOLE)); + $this->consoleAppAdapter->__construct($this->createContext()); $this->expectException(Exception::class); diff --git a/tests/Unit/App/AppContextTest.php b/tests/Unit/App/AppContextTest.php index b6e8ffd5..bdbb16ab 100644 --- a/tests/Unit/App/AppContextTest.php +++ b/tests/Unit/App/AppContextTest.php @@ -5,8 +5,6 @@ use Quantum\Router\RouteCollection; use Quantum\Environment\Environment; use PHPUnit\Framework\TestCase; -use Quantum\App\Enums\AppType; -use InvalidArgumentException; use Quantum\Di\DiContainer; use Quantum\App\AppContext; use Quantum\Config\Config; @@ -16,35 +14,9 @@ class AppContextTest extends TestCase { - public function testAppContextWebMode(): void - { - $context = new AppContext(AppType::WEB, '', new DiContainer()); - - $this->assertSame(AppType::WEB, $context->getMode()); - $this->assertTrue($context->isWebMode()); - $this->assertFalse($context->isConsoleMode()); - } - - public function testAppContextConsoleMode(): void - { - $context = new AppContext(AppType::CONSOLE, '', new DiContainer()); - - $this->assertSame(AppType::CONSOLE, $context->getMode()); - $this->assertFalse($context->isWebMode()); - $this->assertTrue($context->isConsoleMode()); - } - - public function testAppContextRejectsInvalidMode(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid app mode: invalid'); - - new AppContext('invalid', '', new DiContainer()); - } - public function testAppContextBaseDir(): void { - $context = new AppContext(AppType::WEB, '/my/base/dir', new DiContainer()); + $context = new AppContext('/my/base/dir', new DiContainer()); $this->assertSame('/my/base/dir', $context->getBaseDir()); } @@ -52,7 +24,7 @@ public function testAppContextBaseDir(): void public function testAppContextContainer(): void { $container = new DiContainer(); - $context = new AppContext(AppType::WEB, '/tmp', $container); + $context = new AppContext('/tmp', $container); $this->assertSame($container, $context->getContainer()); } @@ -63,7 +35,7 @@ public function testAppContextGetEnvironment(): void $environment = Mockery::mock(Environment::class); $container->set(Environment::class, $environment); - $context = new AppContext(AppType::WEB, '/tmp', $container); + $context = new AppContext('/tmp', $container); $this->assertSame($environment, $context->getEnvironment()); } @@ -74,7 +46,7 @@ public function testAppContextGetConfig(): void $config = Mockery::mock(Config::class); $container->set(Config::class, $config); - $context = new AppContext(AppType::WEB, '/tmp', $container); + $context = new AppContext('/tmp', $container); $this->assertSame($config, $context->getConfig()); } @@ -85,7 +57,7 @@ public function testAppContextGetRequest(): void $request = Mockery::mock(Request::class); $container->set(Request::class, $request); - $context = new AppContext(AppType::WEB, '/tmp', $container); + $context = new AppContext('/tmp', $container); $this->assertSame($request, $context->getRequest()); } @@ -96,7 +68,7 @@ public function testAppContextGetResponse(): void $response = Mockery::mock(Response::class); $container->set(Response::class, $response); - $context = new AppContext(AppType::WEB, '/tmp', $container); + $context = new AppContext('/tmp', $container); $this->assertSame($response, $context->getResponse()); } @@ -107,7 +79,7 @@ public function testAppContextGetRoutes(): void $routes = new RouteCollection(); $container->set(RouteCollection::class, $routes); - $context = new AppContext(AppType::WEB, '/tmp', $container); + $context = new AppContext('/tmp', $container); $this->assertSame($routes, $context->getRoutes()); } diff --git a/tests/Unit/App/AppTest.php b/tests/Unit/App/AppTest.php index 3bd31d76..fcb480e9 100644 --- a/tests/Unit/App/AppTest.php +++ b/tests/Unit/App/AppTest.php @@ -7,7 +7,6 @@ use Quantum\App\Adapters\WebAppAdapter; use Quantum\App\Contracts\AppInterface; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\Enums\AppType; use Quantum\App\App; /** @@ -37,7 +36,7 @@ public function testAppGetAdapter(): void config()->flush(); - $app = new App(new ConsoleAppAdapter($this->createContext(AppType::CONSOLE))); + $app = new App(new ConsoleAppAdapter($this->createContext())); $this->assertInstanceOf(ConsoleAppAdapter::class, $app->getAdapter()); diff --git a/tests/Unit/App/BootPipelineTest.php b/tests/Unit/App/BootPipelineTest.php index be8b9fcc..f425fdae 100644 --- a/tests/Unit/App/BootPipelineTest.php +++ b/tests/Unit/App/BootPipelineTest.php @@ -5,7 +5,6 @@ use Quantum\App\Contracts\BootStageInterface; use Quantum\App\BootPipeline; use Quantum\App\AppContext; -use Quantum\App\Enums\AppType; use Quantum\Di\DiContainer; use PHPUnit\Framework\TestCase; use InvalidArgumentException; @@ -30,7 +29,7 @@ public function testPipelineRunsStagesInOrder(): void }); $pipeline = new BootPipeline([$stage1, $stage2, $stage3]); - $pipeline->run(new AppContext(AppType::WEB, '', new DiContainer())); + $pipeline->run(new AppContext('', new DiContainer())); $this->assertSame(['first', 'second', 'third'], $log); } @@ -38,23 +37,23 @@ public function testPipelineRunsStagesInOrder(): void public function testEmptyPipelineRunsWithoutError(): void { $pipeline = new BootPipeline([]); - $pipeline->run(new AppContext(AppType::WEB, '', new DiContainer())); + $pipeline->run(new AppContext('', new DiContainer())); $this->assertTrue(true); } public function testPipelinePassesContextToStages(): void { - $receivedMode = null; + $receivedBaseDir = null; - $stage = $this->createStage(function (AppContext $context) use (&$receivedMode) { - $receivedMode = $context->getMode(); + $stage = $this->createStage(function (AppContext $context) use (&$receivedBaseDir) { + $receivedBaseDir = $context->getBaseDir(); }); $pipeline = new BootPipeline([$stage]); - $pipeline->run(new AppContext(AppType::CONSOLE, '', new DiContainer())); + $pipeline->run(new AppContext('/test/dir', new DiContainer())); - $this->assertSame(AppType::CONSOLE, $receivedMode); + $this->assertSame('/test/dir', $receivedBaseDir); } public function testPipelinePropagatesException(): void @@ -67,7 +66,7 @@ public function testPipelinePropagatesException(): void $this->expectExceptionMessage('Stage failed'); $pipeline = new BootPipeline([$stage]); - $pipeline->run(new AppContext(AppType::WEB, '', new DiContainer())); + $pipeline->run(new AppContext('', new DiContainer())); } public function testPipelineRejectsInvalidStage(): void diff --git a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php index 5c1365f0..18977ae5 100644 --- a/tests/Unit/App/Stages/LoadEnvironmentStageTest.php +++ b/tests/Unit/App/Stages/LoadEnvironmentStageTest.php @@ -5,7 +5,6 @@ use Quantum\App\Stages\LoadEnvironmentStage; use Quantum\App\Stages\LoadHelpersStage; use Quantum\Tests\Unit\AppTestCase; -use Quantum\App\Enums\AppType; class LoadEnvironmentStageTest extends AppTestCase { @@ -28,16 +27,4 @@ public function testLoadEnvironmentStageLoadsEnvVars(): void $this->assertNotEmpty(env('APP_KEY')); } - - public function testLoadEnvironmentStageSetsMutableForConsole(): void - { - $consoleContext = $this->createContext(AppType::CONSOLE); - - (new LoadHelpersStage())->process($consoleContext); - - $stage = new LoadEnvironmentStage(); - $stage->process($consoleContext); - - $this->assertNotEmpty(env('APP_KEY')); - } } diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 30a9cde7..09bed980 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -51,9 +51,9 @@ public function tearDown(): void $this->clearAppContext(); } - protected function createContext(string $mode = AppType::WEB): AppContext + protected function createContext(): AppContext { - $context = new AppContext($mode, PROJECT_ROOT, new DiContainer()); + $context = new AppContext(PROJECT_ROOT, new DiContainer()); App::setContext($context); return $context; From 7318132b8b9417d0c790aa761b2ac9337027b53d Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:02:49 +0400 Subject: [PATCH 77/77] [#373] Clean up import ordering and consolidate docblock @throws annotations Made-with: Cursor --- src/Mailer/Adapters/SmtpAdapter.php | 4 ++-- src/Model/Factories/ModelFactory.php | 12 +----------- src/Paginator/Adapters/ModelPaginator.php | 2 +- src/Paginator/Traits/PaginatorTrait.php | 2 +- src/Renderer/Factories/RendererFactory.php | 10 ++-------- src/Tracer/ErrorHandler.php | 2 +- 6 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/Mailer/Adapters/SmtpAdapter.php b/src/Mailer/Adapters/SmtpAdapter.php index 923ab61c..fba99171 100644 --- a/src/Mailer/Adapters/SmtpAdapter.php +++ b/src/Mailer/Adapters/SmtpAdapter.php @@ -16,14 +16,14 @@ namespace Quantum\Mailer\Adapters; -use Quantum\Di\Exceptions\DiException; use Quantum\Mailer\Contracts\MailerInterface; +use Quantum\Di\Exceptions\DiException; use Quantum\Mailer\Traits\MailerTrait; use PHPMailer\PHPMailer\PHPMailer; use Quantum\Debugger\Debugger; use PHPMailer\PHPMailer\SMTP; -use Exception; use ReflectionException; +use Exception; /** * class SmtpAdapter diff --git a/src/Model/Factories/ModelFactory.php b/src/Model/Factories/ModelFactory.php index 52fd5ba7..f7ad35fe 100644 --- a/src/Model/Factories/ModelFactory.php +++ b/src/Model/Factories/ModelFactory.php @@ -70,18 +70,8 @@ public static function get(string $modelClass): Model * Creates anonymous dynamic model * @param array $foreignKeys * @param array $hidden - * @return DbModel|BaseException - */ - - /** - * @param string $table - * @param string $modelName - * @param string $idColumn - * @param array $foreignKeys - * @param array $hidden - * @return DbModel * @throws DiException|BaseException|ReflectionException - */ + */ public static function createDynamicModel( string $table, string $modelName = '@anonymous', diff --git a/src/Paginator/Adapters/ModelPaginator.php b/src/Paginator/Adapters/ModelPaginator.php index 060b33df..b4bac85c 100644 --- a/src/Paginator/Adapters/ModelPaginator.php +++ b/src/Paginator/Adapters/ModelPaginator.php @@ -16,10 +16,10 @@ namespace Quantum\Paginator\Adapters; -use Quantum\Di\Exceptions\DiException; use Quantum\Paginator\Contracts\PaginatorInterface; use Quantum\Paginator\Traits\PaginatorTrait; use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Model\ModelCollection; use Quantum\Model\DbModel; use Quantum\Model\Model; diff --git a/src/Paginator/Traits/PaginatorTrait.php b/src/Paginator/Traits/PaginatorTrait.php index 85a5eb15..460032a8 100644 --- a/src/Paginator/Traits/PaginatorTrait.php +++ b/src/Paginator/Traits/PaginatorTrait.php @@ -17,8 +17,8 @@ namespace Quantum\Paginator\Traits; use Quantum\Config\Exceptions\ConfigException; -use Quantum\Lang\Exceptions\LangException; use Quantum\Loader\Exceptions\LoaderException; +use Quantum\Lang\Exceptions\LangException; use Quantum\Paginator\Enums\Pagination; use Quantum\Di\Exceptions\DiException; use ReflectionException; diff --git a/src/Renderer/Factories/RendererFactory.php b/src/Renderer/Factories/RendererFactory.php index 793409ab..4da6c59f 100644 --- a/src/Renderer/Factories/RendererFactory.php +++ b/src/Renderer/Factories/RendererFactory.php @@ -46,10 +46,7 @@ class RendererFactory private array $instances = []; /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public static function get(?string $adapter = null): Renderer { @@ -61,10 +58,7 @@ public static function get(?string $adapter = null): Renderer } /** - * @throws BaseException - * @throws ConfigException - * @throws DiException - * @throws ReflectionException + * @throws ConfigException|BaseException|DiException|ReflectionException */ public function resolve(?string $adapter = null): Renderer { diff --git a/src/Tracer/ErrorHandler.php b/src/Tracer/ErrorHandler.php index a91750e4..5c4ed3b8 100644 --- a/src/Tracer/ErrorHandler.php +++ b/src/Tracer/ErrorHandler.php @@ -16,7 +16,6 @@ namespace Quantum\Tracer; -use Exception; use Quantum\Storage\Contracts\LocalFilesystemAdapterInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Quantum\Renderer\Exceptions\RendererException; @@ -31,6 +30,7 @@ use ErrorException; use ParseError; use Throwable; +use Exception; /** * Class ErrorHandler