From cf4223afdf3245ee77077b6e1b136e5ac26894ea Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 5 Sep 2025 21:42:24 +0400 Subject: [PATCH 1/4] Separate core dependency loading from module dependency loading --- src/App/Adapters/AppAdapter.php | 3 +- src/App/Adapters/WebAppAdapter.php | 18 ++++--- src/App/Config/dependencies.php | 7 +++ src/App/Traits/AppTrait.php | 9 ++++ src/App/Traits/WebAppTrait.php | 11 ++++- src/Di/Di.php | 35 ++------------ src/Module/ModuleLoader.php | 47 ++++++++++++++++++- tests/Unit/Module/ModuleLoaderTest.php | 24 +++++++++- .../modules/Test/Config/dependencies.php | 6 +++ 9 files changed, 112 insertions(+), 48 deletions(-) create mode 100644 src/App/Config/dependencies.php create mode 100644 tests/_root/modules/Test/Config/dependencies.php diff --git a/src/App/Adapters/AppAdapter.php b/src/App/Adapters/AppAdapter.php index 397e1018a..f14b6d668 100644 --- a/src/App/Adapters/AppAdapter.php +++ b/src/App/Adapters/AppAdapter.php @@ -42,8 +42,7 @@ abstract class AppAdapter implements AppInterface */ public function __construct() { - Di::registerDependencies(); - + $this->loadCoreDependencies(); $this->loadComponentHelperFunctions(); $this->loadLibraryHelperFunctions(); $this->loadAppHelperFunctions(); diff --git a/src/App/Adapters/WebAppAdapter.php b/src/App/Adapters/WebAppAdapter.php index 3e5ef0a4d..83d8c4b14 100644 --- a/src/App/Adapters/WebAppAdapter.php +++ b/src/App/Adapters/WebAppAdapter.php @@ -9,24 +9,22 @@ * @author Arman Ag. * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) * @link http://quantum.softberg.org/ - * @since 2.9.7 + * @since 2.9.8 */ namespace Quantum\App\Adapters; -use Quantum\Libraries\Encryption\Exceptions\CryptorException; use Quantum\Libraries\Database\Exceptions\DatabaseException; use Quantum\Libraries\Session\Exceptions\SessionException; -use Quantum\Middleware\Exceptions\MiddlewareException; use Quantum\Router\Exceptions\RouteControllerException; use Quantum\Libraries\Csrf\Exceptions\CsrfException; use Quantum\Libraries\Lang\Exceptions\LangException; -use Quantum\Renderer\Exceptions\RendererException; use Quantum\App\Exceptions\StopExecutionException; use Quantum\Environment\Exceptions\EnvException; use Quantum\Module\Exceptions\ModuleException; use Quantum\Config\Exceptions\ConfigException; use Quantum\Router\Exceptions\RouteException; +use Quantum\Http\Exceptions\HttpException; use Quantum\Middleware\MiddlewareManager; use Quantum\App\Exceptions\BaseException; use Quantum\Di\Exceptions\DiException; @@ -78,22 +76,21 @@ public function __construct() } /** + * Starts the web app * @return int|null * @throws BaseException * @throws ConfigException - * @throws RouteControllerException - * @throws CryptorException * @throws CsrfException * @throws DatabaseException * @throws DebugBarException * @throws DiException + * @throws HttpException * @throws LangException - * @throws MiddlewareException - * @throws ModuleException * @throws ReflectionException + * @throws RouteControllerException * @throws RouteException * @throws SessionException - * @throws RendererException + * @throws ModuleException */ public function start(): ?int { @@ -109,7 +106,8 @@ public function start(): ?int $this->setupErrorHandler(); $this->initializeDebugger(); - $this->loadModulesRoutes(); + $this->loadModules(); + $this->initializeRouter($this->request); info(HookManager::getInstance()->getRegistered(), ['tab' => Debugger::HOOKS]); diff --git a/src/App/Config/dependencies.php b/src/App/Config/dependencies.php new file mode 100644 index 000000000..de9549033 --- /dev/null +++ b/src/App/Config/dependencies.php @@ -0,0 +1,7 @@ + \Quantum\Loader\Loader::class, + \Quantum\Http\Request::class => \Quantum\Http\Request::class, + \Quantum\Http\Response::class => \Quantum\Http\Response::class, +]; \ No newline at end of file diff --git a/src/App/Traits/AppTrait.php b/src/App/Traits/AppTrait.php index c2ee9739e..1faa7757d 100644 --- a/src/App/Traits/AppTrait.php +++ b/src/App/Traits/AppTrait.php @@ -55,6 +55,15 @@ public static function getBaseDir(): string return self::$baseDir; } + protected function loadCoreDependencies() + { + $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 diff --git a/src/App/Traits/WebAppTrait.php b/src/App/Traits/WebAppTrait.php index 7661b9d8e..31fc35214 100644 --- a/src/App/Traits/WebAppTrait.php +++ b/src/App/Traits/WebAppTrait.php @@ -31,6 +31,7 @@ use Quantum\Http\Request; use Quantum\Loader\Setup; use ReflectionException; +use Quantum\Di\Di; /** * Trait WebAppTrait @@ -63,12 +64,18 @@ private function initializeDebugger() } /** + * Load modules * @throws ModuleException * @throws RouteException */ - private function loadModulesRoutes() + private function loadModules() { - $modulesRoutes = ModuleLoader::getInstance()->loadModulesRoutes(); + $moduleLoader = ModuleLoader::getInstance(); + + $modulesDependencies = $moduleLoader->loadModulesDependencies(); + Di::registerDependencies($modulesDependencies); + + $modulesRoutes = $moduleLoader->loadModulesRoutes(); Router::setRoutes($modulesRoutes); } diff --git a/src/Di/Di.php b/src/Di/Di.php index 9b1f168f5..be95475e7 100644 --- a/src/Di/Di.php +++ b/src/Di/Di.php @@ -19,7 +19,6 @@ use ReflectionParameter; use ReflectionFunction; use ReflectionMethod; -use Quantum\App\App; use ReflectionClass; /** @@ -45,11 +44,11 @@ class Di private static $resolving = []; /** - * Loads dependency definitions + * Register dependencies */ - public static function registerDependencies() + public static function registerDependencies(array $dependencies) { - foreach (self::coreDependencies() + self::userDependencies() as $abstract => $concrete) { + foreach ($dependencies as $abstract => $concrete) { if (!self::isRegistered($abstract)) { self::register($concrete, $abstract); } @@ -272,32 +271,4 @@ private static function checkCircularDependency(string $abstract): void throw DiException::circularDependency($chain); } } - - /** - * Loads user defined dependencies - * @return array - */ - private static function userDependencies(): array - { - $userDependencies = App::getBaseDir() . DS . 'shared' . DS . 'config' . DS . 'dependencies.php'; - - if (!file_exists($userDependencies)) { - return []; - } - - return (array)require_once $userDependencies; - } - - /** - * Loads the core dependencies - * @return array - */ - private static function coreDependencies(): array - { - return [ - \Quantum\Loader\Loader::class => \Quantum\Loader\Loader::class, - \Quantum\Http\Request::class => \Quantum\Http\Request::class, - \Quantum\Http\Response::class => \Quantum\Http\Response::class, - ]; - } } diff --git a/src/Module/ModuleLoader.php b/src/Module/ModuleLoader.php index 2d650ead3..a1749a421 100644 --- a/src/Module/ModuleLoader.php +++ b/src/Module/ModuleLoader.php @@ -9,7 +9,7 @@ * @author Arman Ag. * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) * @link http://quantum.softberg.org/ - * @since 2.9.7 + * @since 2.9.8 */ namespace Quantum\Module; @@ -33,6 +33,11 @@ class ModuleLoader { + /** + * @var array + */ + private static $moduleDependencies = []; + /** * @var array */ @@ -101,6 +106,46 @@ public function loadModulesRoutes(): array return $modulesRoutes; } + /** + * @return array + * @throws ModuleException + */ + public function loadModulesDependencies(): array + { + if (empty(self::$moduleConfigs)) { + $this->loadModuleConfig(); + } + + $modulesDependencies = []; + + foreach (self::$moduleConfigs as $module => $options) { + $modulesDependencies = array_merge($modulesDependencies, $this->getModuleDependencies($module)); + } + + return $modulesDependencies; + } + + /** + * @param string $module + * @return array + */ + public function getModuleDependencies(string $module): array + { + if (!isset(self::$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 : []; + } else { + self::$moduleDependencies[$module] = []; + } + } + + return self::$moduleDependencies[$module]; + } + /** * @return array * @throws ModuleException diff --git a/tests/Unit/Module/ModuleLoaderTest.php b/tests/Unit/Module/ModuleLoaderTest.php index 4aedf8aeb..56077ba33 100644 --- a/tests/Unit/Module/ModuleLoaderTest.php +++ b/tests/Unit/Module/ModuleLoaderTest.php @@ -27,7 +27,7 @@ public function testGetInstance() $this->assertInstanceOf(ModuleLoader::class, $this->moduleLoader); } - public function testLoadModulesRoutesWithEnabledModules() + public function testLoadModulesRoutes() { $modulesRoutes = $this->moduleLoader->loadModulesRoutes(); @@ -39,4 +39,26 @@ public function testLoadModulesRoutesWithEnabledModules() $this->assertCount(2, Router::getRoutes()); } + + public function testLoadModulesDependencies() + { + $deps = $this->moduleLoader->loadModulesDependencies(); + + $this->assertIsArray($deps); + + $this->assertArrayHasKey("Quantum\Libraries\Transformer\Transformer", $deps); + $this->assertSame("Shared\Transformers\PostTransformer", $deps["Quantum\Libraries\Transformer\Transformer"]); + + $this->assertArrayHasKey("Quantum\Service\QtService", $deps); + $this->assertSame("Shared\Services\TokenService", $deps["Quantum\Service\QtService"]); + } + + public function testGetModuleConfigs() + { + $configs = $this->moduleLoader->getModuleConfigs(); + + $this->assertIsArray($configs); + $this->assertArrayHasKey('Test', $configs); + $this->assertArrayNotHasKey('Mame', $configs); + } } \ No newline at end of file diff --git a/tests/_root/modules/Test/Config/dependencies.php b/tests/_root/modules/Test/Config/dependencies.php new file mode 100644 index 000000000..5f30468c7 --- /dev/null +++ b/tests/_root/modules/Test/Config/dependencies.php @@ -0,0 +1,6 @@ + \Shared\Transformers\PostTransformer::class, + \Quantum\Service\QtService::class => \Shared\Services\TokenService::class, +]; \ No newline at end of file From e540615079521a077a9402709880c47c262dc851 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:00:19 +0400 Subject: [PATCH 2/4] Correcting unit tests --- src/App/Factories/AppFactory.php | 9 +++++++++ tests/Unit/App/Factories/AppFactoryTest.php | 13 +++++++++++++ tests/Unit/AppTestCase.php | 19 +++++++++++++++++++ tests/Unit/Di/DiTest.php | 5 ----- tests/Unit/Module/ModuleLoaderTest.php | 8 ++++---- .../modules/Test/Config/dependencies.php | 9 +++++++-- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/App/Factories/AppFactory.php b/src/App/Factories/AppFactory.php index 30026c927..2673d20e7 100644 --- a/src/App/Factories/AppFactory.php +++ b/src/App/Factories/AppFactory.php @@ -56,6 +56,15 @@ public static function create(string $type, string $baseDir): App return self::$instances[$type]; } + /** + * @param string $type + * @return void + */ + public static function destroy(string $type) + { + unset(self::$instances[$type]); + } + /** * @param string $type * @param string $baseDir diff --git a/tests/Unit/App/Factories/AppFactoryTest.php b/tests/Unit/App/Factories/AppFactoryTest.php index 0143aea2c..9d9c2aae3 100644 --- a/tests/Unit/App/Factories/AppFactoryTest.php +++ b/tests/Unit/App/Factories/AppFactoryTest.php @@ -60,4 +60,17 @@ public function testAppFactoryReturnsSameInstance() $this->assertSame($app1, $app2); } + + public function testAppFactoryDestroy() + { + $app1 = AppFactory::create(App::WEB, PROJECT_ROOT); + + AppFactory::destroy(App::WEB); + + $app2 = AppFactory::create(App::WEB, PROJECT_ROOT); + + $this->assertInstanceOf(App::class, $app2); + + $this->assertNotSame($app1, $app2); + } } \ No newline at end of file diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 615b49c6d..0ced015c1 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -10,6 +10,7 @@ use Quantum\Loader\Setup; use ReflectionClass; use Quantum\App\App; +use Quantum\Di\Di; abstract class AppTestCase extends TestCase { @@ -18,6 +19,10 @@ abstract class AppTestCase extends TestCase public function setUp(): void { + if (!file_exists(PROJECT_ROOT . DS . '.env.testing')) { + createEnvFile(); + } + AppFactory::create(App::WEB, PROJECT_ROOT); Config::getInstance()->flush(); @@ -29,9 +34,23 @@ public function setUp(): void Config::getInstance() ->load(new Setup('config', 'config')); + $coreDependencies = [ + \Quantum\Loader\Loader::class => \Quantum\Loader\Loader::class, + \Quantum\Http\Request::class => \Quantum\Http\Request::class, + \Quantum\Http\Response::class => \Quantum\Http\Response::class, + ]; + + Di::registerDependencies($coreDependencies); + $this->fs = FileSystemFactory::get(); } + public function tearDown(): void + { + AppFactory::destroy(App::WEB); + Di::reset(); + } + 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 4fefae2ac..810275f7e 100644 --- a/tests/Unit/Di/DiTest.php +++ b/tests/Unit/Di/DiTest.php @@ -69,9 +69,6 @@ class DiTest extends AppTestCase public function setUp(): void { parent::setUp(); - - Di::reset(); - Di::registerDependencies(); } public function testDiRegisterDependency() @@ -240,8 +237,6 @@ public function testDiReset() $this->assertEmpty($dependenciesProperty->getValue()); $this->assertEmpty($containerProperty->getValue()); - - Di::registerDependencies(); } } } \ No newline at end of file diff --git a/tests/Unit/Module/ModuleLoaderTest.php b/tests/Unit/Module/ModuleLoaderTest.php index 56077ba33..beeb2dfa2 100644 --- a/tests/Unit/Module/ModuleLoaderTest.php +++ b/tests/Unit/Module/ModuleLoaderTest.php @@ -46,11 +46,11 @@ public function testLoadModulesDependencies() $this->assertIsArray($deps); - $this->assertArrayHasKey("Quantum\Libraries\Transformer\Transformer", $deps); - $this->assertSame("Shared\Transformers\PostTransformer", $deps["Quantum\Libraries\Transformer\Transformer"]); + $this->assertArrayHasKey("Quantum\Libraries\Transformer\Contracts\TransformerInterface", $deps); + $this->assertSame("Quantum\Tests\_root\modules\Test\Transformers\PostTransformer", $deps["Quantum\Libraries\Transformer\Contracts\TransformerInterface"]); - $this->assertArrayHasKey("Quantum\Service\QtService", $deps); - $this->assertSame("Shared\Services\TokenService", $deps["Quantum\Service\QtService"]); + $this->assertArrayHasKey("Quantum\Libraries\Storage\Contracts\TokenServiceInterface", $deps); + $this->assertSame("Quantum\Tests\_root\shared\Services\TokenService", $deps["Quantum\Libraries\Storage\Contracts\TokenServiceInterface"]); } public function testGetModuleConfigs() diff --git a/tests/_root/modules/Test/Config/dependencies.php b/tests/_root/modules/Test/Config/dependencies.php index 5f30468c7..d44705bb1 100644 --- a/tests/_root/modules/Test/Config/dependencies.php +++ b/tests/_root/modules/Test/Config/dependencies.php @@ -1,6 +1,11 @@ \Shared\Transformers\PostTransformer::class, - \Quantum\Service\QtService::class => \Shared\Services\TokenService::class, + TransformerInterface::class => PostTransformer::class, + TokenServiceInterface::class => TokenService::class, ]; \ No newline at end of file From 6cddf2b78e147159d72669f1dd33d1b510794f86 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:06:27 +0400 Subject: [PATCH 3/4] Pushing missed helper transformer --- .../Test/Transformers/PostTransformer.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/_root/modules/Test/Transformers/PostTransformer.php diff --git a/tests/_root/modules/Test/Transformers/PostTransformer.php b/tests/_root/modules/Test/Transformers/PostTransformer.php new file mode 100644 index 000000000..11678e2b4 --- /dev/null +++ b/tests/_root/modules/Test/Transformers/PostTransformer.php @@ -0,0 +1,42 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 2.9.8 + */ + +namespace Quantum\Tests\_root\modules\Test\Transformers; + +use Quantum\Libraries\Transformer\Contracts\TransformerInterface; + +/** + * Class PostTransformer + * @package Modules\Api + */ +class PostTransformer implements TransformerInterface +{ + + /** + * Transforms the post data + * @param $item + * @return mixed + */ + public function transform($item): array + { + return [ + 'uuid' => $item->uuid, + 'title' => $item->title, + 'content' => markdown_to_html($item->content, true), + 'image' => $item->image ? $item->user_directory . '/' . $item->image : null, + 'date' => date('Y/m/d H:i', strtotime($item->updated_at)), + 'author' => $item->firstname . ' ' . $item->lastname + ]; + } +} \ No newline at end of file From c5a9836b8984e4102a623721e09fa1acae079c1b Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Tue, 30 Sep 2025 13:05:33 +0400 Subject: [PATCH 4/4] Changing folders names to lowercase --- tests/_root/modules/Test/{Config => config}/dependencies.php | 0 .../modules/Test/{Resources => resources}/lang/en/custom.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/_root/modules/Test/{Config => config}/dependencies.php (100%) rename tests/_root/modules/Test/{Resources => resources}/lang/en/custom.php (100%) diff --git a/tests/_root/modules/Test/Config/dependencies.php b/tests/_root/modules/Test/config/dependencies.php similarity index 100% rename from tests/_root/modules/Test/Config/dependencies.php rename to tests/_root/modules/Test/config/dependencies.php diff --git a/tests/_root/modules/Test/Resources/lang/en/custom.php b/tests/_root/modules/Test/resources/lang/en/custom.php similarity index 100% rename from tests/_root/modules/Test/Resources/lang/en/custom.php rename to tests/_root/modules/Test/resources/lang/en/custom.php