From bc2ad0ee4e38111726ba189f924dd899c228888d Mon Sep 17 00:00:00 2001 From: Angger Pradana Date: Mon, 20 May 2024 08:55:58 +0700 Subject: [PATCH] feat, refactor: application with bootstrapper (#327) * feat, refactor: application with bootstrapper - add karnel register appliaction bootstrapper - add BootProvider: to call `Application::bootProvider` - add RegisterProvider: to call `Application::registerProvider` * refactor: use native Application Container method * refactor: remove booted check. register run before boot * feat: application callbacks after and before boot --- src/System/Integrate/Application.php | 103 ++++++++++++++++-- .../Integrate/Bootstrap/BootProviders.php | 15 +++ .../Integrate/Bootstrap/RegisterProviders.php | 15 +++ src/System/Integrate/Console/Karnel.php | 22 ++++ src/System/Integrate/Http/Karnel.php | 21 ++++ tests/Integrate/ApplicationTest.php | 65 +++++++++++ .../Integrate/Bootstrap/BootProvidersTest.php | 20 ++++ .../Bootstrap/RegisterProvidersTest.php | 24 ++++ tests/Integrate/Console/KarnelTest.php | 8 ++ tests/Integrate/Http/KarnelTest.php | 11 +- 10 files changed, 294 insertions(+), 10 deletions(-) create mode 100644 src/System/Integrate/Bootstrap/BootProviders.php create mode 100644 src/System/Integrate/Bootstrap/RegisterProviders.php create mode 100644 tests/Integrate/Bootstrap/BootProvidersTest.php create mode 100644 tests/Integrate/Bootstrap/RegisterProvidersTest.php diff --git a/src/System/Integrate/Application.php b/src/System/Integrate/Application.php index 8cd03ba3..f9b4d4f8 100644 --- a/src/System/Integrate/Application.php +++ b/src/System/Integrate/Application.php @@ -170,6 +170,11 @@ final class Application extends Container */ private $isBooted = false; + /** + * Detect application has been bootstrapped. + */ + private bool $isBootstrapped = false; + /** * Terminate callback register. * @@ -177,6 +182,20 @@ final class Application extends Container */ private $terminateCallback = []; + /** + * Registered booting callback. + * + * @var callable[] + */ + protected array $booting_callbacks = []; + + /** + * Registered booted callback. + * + * @var callable[] + */ + protected array $booted_callbacks = []; + /** * Contructor. * @@ -198,10 +217,6 @@ public function __construct(string $base_path) // register base provider $this->register(IntegrateServiceProvider::class); - // boot provider - $this->registerProvider(); - $this->bootProvider(); - // register container alias $this->registerAlias(); } @@ -829,8 +844,38 @@ public function isDev() return $this->environment() === 'dev'; } + /** + * Detect appliaction has been booted. + */ + public function isBooted(): bool + { + return $this->isBooted; + } + + /** + * Detect application has been bootstrapped. + */ + public function isBootstrapped(): bool + { + return $this->isBootstrapped; + } + // core region + /** + * Bootstrapper. + * + * @param array $bootstrappers + */ + public function bootstrapWith($bootstrappers): void + { + $this->isBootstrapped = true; + + foreach ($bootstrappers as $bootstrapper) { + $this->make($bootstrapper)->bootstrap($this); + } + } + /** * Boot service provider. * @@ -842,6 +887,8 @@ public function bootProvider() return; } + $this->callBootCallbacks($this->booting_callbacks); + foreach ($this->providers as $provider) { if (in_array($provider, $this->booted_providers)) { continue; @@ -851,6 +898,8 @@ public function bootProvider() $this->booted_providers[] = $provider; } + $this->callBootCallbacks($this->booted_callbacks); + $this->isBooted = true; } @@ -861,10 +910,6 @@ public function bootProvider() */ public function registerProvider() { - if (!$this->isBooted) { - return; - } - foreach ($this->providers as $provider) { if (in_array($provider, $this->looded_providers)) { continue; @@ -876,6 +921,46 @@ public function registerProvider() } } + /** + * Call the registered booting callbacks. + * + * @param callable[] $bootCallBacks + */ + public function callBootCallbacks($bootCallBacks): void + { + $index = 0; + + while ($index < count($bootCallBacks)) { + $this->call($bootCallBacks[$index]); + + $index++; + } + } + + /** + * Add booting call back, call before boot is calling. + * + * @param callable[] $callback + */ + public function bootingCallback($callback): void + { + $this->booting_callbacks[] = $callback; + } + + /** + * Add booted call back, call after boot is called. + * + * @param callable[] $callback + */ + public function bootedCallback($callback): void + { + $this->booted_callbacks[] = $callback; + + if ($this->isBooted()) { + $this->call($callback); + } + } + /** * Flush or reset application (static). */ @@ -887,6 +972,8 @@ public function flush(): void $this->looded_providers = []; $this->booted_providers = []; $this->terminateCallback = []; + $this->booting_callbacks = []; + $this->booted_callbacks = []; parent::flush(); } diff --git a/src/System/Integrate/Bootstrap/BootProviders.php b/src/System/Integrate/Bootstrap/BootProviders.php new file mode 100644 index 00000000..596967ec --- /dev/null +++ b/src/System/Integrate/Bootstrap/BootProviders.php @@ -0,0 +1,15 @@ +bootProvider(); + } +} diff --git a/src/System/Integrate/Bootstrap/RegisterProviders.php b/src/System/Integrate/Bootstrap/RegisterProviders.php new file mode 100644 index 00000000..df732159 --- /dev/null +++ b/src/System/Integrate/Bootstrap/RegisterProviders.php @@ -0,0 +1,15 @@ +registerProvider(); + } +} diff --git a/src/System/Integrate/Console/Karnel.php b/src/System/Integrate/Console/Karnel.php index 6dfa480d..3dfcda85 100644 --- a/src/System/Integrate/Console/Karnel.php +++ b/src/System/Integrate/Console/Karnel.php @@ -6,13 +6,25 @@ use System\Console\Style\Style; use System\Integrate\Application; +use System\Integrate\Bootstrap\BootProviders; +use System\Integrate\Bootstrap\RegisterProviders; class Karnel { + /** + * Application Container. + */ protected Application $app; + /** @var int concole exit status */ protected $exit_code; + /** @var array Apllication bootstrap register. */ + protected array $bootstrappers = [ + RegisterProviders::class, + BootProviders::class, + ]; + /** * Set instance. */ @@ -34,6 +46,8 @@ public function handle($arguments) $baseArgs = $arguments[1] ?? '--help'; $commands = []; + $this->bootstrap(); + foreach ($this->commands() as $cmd) { $commands = array_merge($commands, $cmd->patterns(), $cmd->cmd()); @@ -78,6 +92,14 @@ public function handle($arguments) return $this->exit_code = 1; } + /** + * Register bootstraper application. + */ + public function bootstrap(): void + { + $this->app->bootstrapWith($this->bootstrappers); + } + /** * Return similar from given array, compare with key. * diff --git a/src/System/Integrate/Http/Karnel.php b/src/System/Integrate/Http/Karnel.php index ac44acd3..f3818ba9 100644 --- a/src/System/Integrate/Http/Karnel.php +++ b/src/System/Integrate/Http/Karnel.php @@ -7,12 +7,17 @@ use System\Http\Request; use System\Http\Response; use System\Integrate\Application; +use System\Integrate\Bootstrap\BootProviders; +use System\Integrate\Bootstrap\RegisterProviders; use System\Integrate\Exceptions\Handler; use System\Integrate\Http\Middleware\MaintenanceMiddleware; use System\Router\Router; class Karnel { + /** + * Application Container. + */ protected Application $app; /** @var array Global middleware */ @@ -23,6 +28,12 @@ class Karnel /** @var array Middleware has register */ protected $middleware_used = []; + /** @var array Apllication bootstrap register. */ + protected array $bootstrappers = [ + RegisterProviders::class, + BootProviders::class, + ]; + /** * Set instance. */ @@ -43,6 +54,8 @@ public function handle(Request $request) $this->app->set(Request::class, $request); try { + $this->bootstrap(); + $dispatcher = $this->dispatcher($request); $pipeline = array_reduce( @@ -62,6 +75,14 @@ public function handle(Request $request) return $response; } + /** + * Register bootstraper application. + */ + public function bootstrap(): void + { + $this->app->bootstrapWith($this->bootstrappers); + } + /** * Terminate Requesr and Response. */ diff --git a/tests/Integrate/ApplicationTest.php b/tests/Integrate/ApplicationTest.php index e048908f..4e68739e 100644 --- a/tests/Integrate/ApplicationTest.php +++ b/tests/Integrate/ApplicationTest.php @@ -153,6 +153,63 @@ public function itCanAbortApplication() (new Application(__DIR__))->abort(500); } + /** @test */ + public function itCanBootstrapWith() + { + $app = new Application(__DIR__); + + ob_start(); + $app->bootstrapWith([ + TestBootstrapProvider::class, + ]); + $out = ob_get_clean(); + + $this->assertEquals($out, 'TestBootstrapProvider::bootstrap'); + $this->assertTrue($app->isBootstrapped()); + } + + /** @test */ + public function itCanAddCallBacksBeforeAndAfterBoot() + { + $app = new Application(__DIR__); + + $app->bootedCallback(static function () { + echo 'booted01'; + }); + $app->bootedCallback(static function () { + echo 'booted02'; + }); + $app->bootingCallback(static function () { + echo 'booting01'; + }); + $app->bootingCallback(static function () { + echo 'booting02'; + }); + + ob_start(); + $app->bootProvider(); + $out = ob_get_clean(); + + $this->assertEquals($out, 'booting01booting02booted01booted02'); + $this->assertTrue($app->isBooted()); + } + + public function itCanAddCallImediatllyIfApplicationAlredyBooted() + { + $app = new Application(__DIR__); + + $app->bootProvider(); + + ob_start(); + $app->bootedCallback(static function () { + echo 'imediatly call'; + }); + $out = ob_get_clean(); + + $this->assertTrue($app->isBooted()); + $this->assertEquals($out, 'imediatly call'); + } + /** @test */ public function itCanCallDeprecatedMethod() { @@ -238,3 +295,11 @@ private function defaultConfigs() ]; } } + +class TestBootstrapProvider +{ + public function bootstrap(Application $app): void + { + echo __CLASS__ . '::' . __FUNCTION__; + } +} diff --git a/tests/Integrate/Bootstrap/BootProvidersTest.php b/tests/Integrate/Bootstrap/BootProvidersTest.php new file mode 100644 index 00000000..d45c1bda --- /dev/null +++ b/tests/Integrate/Bootstrap/BootProvidersTest.php @@ -0,0 +1,20 @@ +assertFalse($app->isBooted()); + $app->bootstrapWith([BootProviders::class]); + $this->assertTrue($app->isBooted()); + } +} diff --git a/tests/Integrate/Bootstrap/RegisterProvidersTest.php b/tests/Integrate/Bootstrap/RegisterProvidersTest.php new file mode 100644 index 00000000..bd124afa --- /dev/null +++ b/tests/Integrate/Bootstrap/RegisterProvidersTest.php @@ -0,0 +1,24 @@ +register(TestRegisterServiceProvider::class); + $app->bootstrapWith([BootProviders::class]); + $this->assertCount(2, (fn () => $this->{'looded_providers'})->call($app), '1 from defult provider, 1 from this test.'); + } +} + +class TestRegisterServiceProvider extends ServiceProvider +{ +} diff --git a/tests/Integrate/Console/KarnelTest.php b/tests/Integrate/Console/KarnelTest.php index c8589418..d83ceeef 100644 --- a/tests/Integrate/Console/KarnelTest.php +++ b/tests/Integrate/Console/KarnelTest.php @@ -203,6 +203,14 @@ public function itCanGivenClosetCommand() $condition = Str::contains($out, 'use:full'); $this->assertTrue($condition); } + + /** @test */ + public function itCanBootstrap() + { + $this->assertFalse($this->app->isBootstrapped()); + $this->app->make(Karnel::class)->bootstrap(); + $this->assertTrue($this->app->isBootstrapped()); + } } class NormalCommand extends Karnel diff --git a/tests/Integrate/Http/KarnelTest.php b/tests/Integrate/Http/KarnelTest.php index 1928fef7..0689f5d3 100644 --- a/tests/Integrate/Http/KarnelTest.php +++ b/tests/Integrate/Http/KarnelTest.php @@ -10,9 +10,8 @@ final class KarnelTest extends TestCase { - private $app; + private Application $app; private $karnel; - private $middleware; protected function setUp(): void { @@ -80,4 +79,12 @@ public function itCanRedirectByMiddleware() $test->__toString() ); } + + /** @test */ + public function itCanBootstrap() + { + $this->assertFalse($this->app->isBootstrapped()); + $this->app->make(Karnel::class)->bootstrap(); + $this->assertTrue($this->app->isBootstrapped()); + } }