+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $app->registerInitializer(new ClassThatImplementsInitializer);
+ * $app->registerDisposer(new ClassThatImplementsDisposer);
+ *
+ * $app->kernel()->boot();
+ * $app->initialize();
+ *
+ * // write code here
+ *
+ * $app->dispose();
+ * $app->kernel()->shutdown();
+ *
+ *
+ * @since 1.0.0
+ */
+final readonly class Application {
+
+ /**
+ * ### Application lifecycle engine
+ * @since 1.0.0
+ *
+ * @var Engine+ * Application context. + *
+ * @param \FireHub\Kernel\Kernel $kernel [optional]+ * Parent kernel. A new kernel is created by default. + *
+ * @param \FireHub\Core\Boundary\Container $container [optional]+ * Application dependency injection container. A new container is created by default. + *
+ * @param \FireHub\Module\Manager $modules [optional]+ * Application module manager. + *
+ * + * @return void + */ + public function __construct ( + private Context $context, + private Kernel $kernel = new Kernel, + private ContainerBoundary $container = new Container, + private ModuleManager $modules = new ModuleManager + ) { + + $this->lifecycle = new Engine( + State::CREATED, + $this->kernel->dependencyBoundary() + ); + + } + + /** + * ### Gets the application lifecycle dependency boundary + * + * Exposes the application as a parent lifecycle for dependent runtime executions. + * + * Dependencies can only be acquired while the application is initialized. + * + * @since 1.0.0 + * + * @uses \FireHub\Kernel\Lifecycle\Engine::dependencyBoundary() To create the dependency boundary. + * + * @return \FireHub\Kernel\Lifecycle\Dependency\Boundary Application lifecycle dependency boundary. + */ + public function dependencyBoundary ():Boundary { + + return $this->lifecycle->dependencyBoundary( + fn (State $state):bool => $state === State::INITIALIZED + ); + + } + + /** + * ### Registers an application initializer + * + * Registers an initializer for execution during application initialization. + * + * Initializers are executed according to their configured execution order. Registration is permitted only before + * initialization begins. + * + *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $app->registerInitializer(new ClassThatImplementsInitializer);
+ *
+ *
+ * @since 1.0.0
+ *
+ * @uses \FireHub\Kernel\Lifecycle\Engine::registerStartup() To register an initializer.
+ * @uses \FireHub\Application\Initializer::initialize() To execute the initializer.
+ *
+ * @param \FireHub\Application\Initializer $initializer + * Initializer to register. + *
+ * @param int $order [optional]+ * Execution order. + *
+ * + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If registration is not allowed. + * + * @return void + */ + public function registerInitializer (Initializer $initializer, int $order = 0):void { + + try { + + $this->lifecycle->registerStartup( + function () use ($initializer):void { + + $initializer->initialize($this); + + }, + $order + ); + + } catch (InvalidLifecycleStateException $exception) { + + throw new InvalidApplicationStateException( + 'Cannot register an initializer in the current application state.', + previous: $exception + ); + + } + + } + + /** + * ### Registers an application disposer + * + * Registers a disposer for execution during application disposal. + * + * Disposers are executed according to their configured execution order. Registration is permitted only before + * initialization begins. + * + *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $app->registerDisposer(new ClassThatImplementsDisposer);
+ *
+ *
+ * @since 1.0.0
+ *
+ * @uses \FireHub\Kernel\Lifecycle\Engine::registerShutdown() To register a disposer.
+ * @uses \FireHub\Application\Disposer::dispose() To execute the disposer.
+ *
+ * @param \FireHub\Application\Disposer $disposer + * Disposer to register. + *
+ * @param int $order+ * [optional] Execution order. + *
+ * + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If registration is not allowed. + * + * @return void + */ + public function registerDisposer (Disposer $disposer, int $order = 0):void { + + try { + + $this->lifecycle->registerShutdown( + function () use ($disposer):void { + + $disposer->dispose($this); + + }, + $order + ); + + } catch (InvalidLifecycleStateException $exception) { + + throw new InvalidApplicationStateException( + 'Cannot register a disposer in the current application state.', + previous: $exception + ); + + } + + } + + /** + * ### Initializes the application + * + * Executes registered initializers and prepares the application for execution. + * + * The kernel must be fully booted before application initialization can begin. + * + * Initialization stops immediately when an initializer fails. + * + * Calling this method after successful initialization has no effect. + * + *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $app->initialize();
+ *
+ *
+ * @since 1.0.0
+ *
+ * @uses \FireHub\Kernel\Lifecycle\Engine::state() To get the current state.
+ * @uses \FireHub\Kernel\Lifecycle\Engine::start() To execute registered initializers.
+ * @uses \FireHub\Kernel\Lifecycle\Engine::execute() To execute a callback.
+ *
+ * @throws \FireHub\Application\Exception\InvalidApplicationStateException If initialization is not allowed.
+ * @throws \FireHub\Application\Exception\ApplicationInitializationException If an initializer fails.
+ *
+ * @return void
+ */
+ public function initialize ():void {
+
+ if ($this->state() === State::INITIALIZED) return;
+
+ try {
+
+ $this->lifecycle->execute(
+ callback: fn() => $this->lifecycle->start(),
+ from: [State::CREATED],
+ success: State::INITIALIZED,
+ failure: State::INITIALIZATION_FAILED,
+ entering: State::INITIALIZING,
+ acquire_parent: true
+ );
+
+ } catch (InvalidLifecycleStateException $exception) {
+
+ throw new InvalidApplicationStateException(
+ 'Cannot initialize the application in the current state.',
+ previous: $exception
+ );
+
+ } catch (LifecycleExecutionException $exception) {
+
+ throw new ApplicationInitializationException(
+ previous: $exception->getPrevious() ?? $exception
+ );
+
+ }
+
+ }
+
+ /**
+ * ### Disposes the application
+ *
+ * Executes registered disposers and releases resources managed by the application.
+ *
+ * Disposal may be performed after successful initialization, after initialization failure, or before
+ * initialization begins.
+ *
+ * All registered disposers are attempted even when an individual disposer fails.
+ *
+ * Calling this method after successful disposal has no effect.
+ *
+ *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $app->dispose();
+ *
+ *
+ * @since 1.0.0
+ *
+ * @uses \FireHub\Kernel\Lifecycle\Engine::state() To get the current state.
+ * @uses \FireHub\Kernel\Lifecycle\Engine::execute() To execute a callback.
+ * @uses \FireHub\Kernel\Lifecycle\Engine::stop() To execute registered disposers.
+ *
+ * @throws \FireHub\Application\Exception\InvalidApplicationStateException If disposal is not allowed.
+ * @throws \FireHub\Application\Exception\ApplicationDisposalException If a disposer fails.
+ *
+ * @return void
+ */
+ public function dispose ():void {
+
+ if ($this->state() === State::DISPOSED) return;
+
+ try {
+
+ $this->lifecycle->execute(
+ callback: fn() => $this->lifecycle->stop(),
+ from: [
+ State::CREATED,
+ State::INITIALIZED,
+ State::INITIALIZATION_FAILED
+ ],
+ success: State::DISPOSED,
+ failure: State::DISPOSAL_FAILED,
+ entering: State::DISPOSING,
+ acquire_parent: true,
+ release_parent: true,
+ require_no_dependents: true
+ );
+
+ } catch (InvalidLifecycleStateException $exception) {
+
+ throw new InvalidApplicationStateException(
+ 'Cannot dispose the application in the current state.',
+ previous: $exception
+ );
+
+ } catch (LifecycleExecutionException $exception) {
+
+ throw new ApplicationDisposalException(
+ previous: $exception->getPrevious() ?? $exception
+ );
+
+ }
+
+ }
+
+ /**
+ * ### Executes an application operation
+ *
+ * Executes a callback within an isolated dependency injection scope.
+ *
+ * The application must be initialized before execution begins.
+ *
+ * Each execution creates a new dependency injection scope and execution context.
+ *
+ * Scoped services are reused within the execution but remain isolated from other executions. Singleton services
+ * remain shared through the originating application container.
+ *
+ * Acquires an application lifecycle dependency to prevent disposal while execution is active.
+ *
+ * The execution scope is automatically closed after the callback completes, including when an exception is thrown.
+ *
+ * The lifecycle dependency is released after scope cleanup.
+ *
+ *
+ * $result = $app->execute(
+ * function () use (ExecutionContext $execution):void {
+ *
+ * $scope = $execution->scope();
+ *
+ * $service = $scope->get(Test::class);
+ *
+ * return '';
+ *
+ * }
+ * );
+ *
+ *
+ * @since 1.0.0
+ *
+ * @uses \FireHub\Application\Application::dependencyBoundary() To acquire a lifecycle dependency.
+ * @uses \FireHub\Kernel\Lifecycle\Dependency\Lease::release() To release the lifecycle dependency.
+ *
+ * @param Closure(\FireHub\Application\ExecutionContext):mixed $callback + * Operation to execute. + *
+ * + * @throws \FireHub\Kernel\Lifecycle\Exception\InvalidLifecycleStateException If the application is not available. + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If the application is not initialized. + * + * @return mixed Operation result. + */ + public function execute (Closure $callback):mixed { + + try { + + $lease = $this->dependencyBoundary()->acquire(); + + } catch (InvalidLifecycleStateException $exception) { + + throw new InvalidApplicationStateException( + 'Cannot execute an operation in the current application state.', + previous: $exception + ); + + } + + try { + + $scope = $this->container->createScope(); + + try { + + return $callback( + new ExecutionContext($this, $scope) + ); + + } finally { + + $scope->close(); + + } + + } finally { + + $lease->release(); + + } + + } + + /** + * ### Gets the current application lifecycle state + * + * Returns the application's own lifecycle state directly from its lifecycle engine. + * + * No conversion from kernel states is performed. + * + *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $app->state();
+ *
+ * // FireHub\Application\State::CREATED
+ *
+ * $app->initialize();
+ * $app->state();
+ *
+ * // FireHub\Application\State::INITIALIZED
+ *
+ * $app->dispose();
+ * $app->state();
+ *
+ * // FireHub\Application\State::DISPOSED
+ *
+ *
+ * @since 1.0.0
+ *
+ * @uses \FireHub\Kernel\Lifecycle\Engine::state() To get the current state.
+ *
+ * @return \FireHub\Application\State Current application lifecycle state.
+ */
+ public function state ():State {
+
+ return $this->lifecycle->state();
+
+ }
+
+ /**
+ * ### Gets the application kernel
+ *
+ * Returns the parent kernel on which the application lifecycle depends.
+ * @since 1.0.0
+ *
+ * @return \FireHub\Kernel\Kernel Application kernel.
+ */
+ public function kernel ():Kernel {
+
+ return $this->kernel;
+
+ }
+
+ /**
+ * ### Gets the application execution context
+ *
+ *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ *
+ * $app = new Application(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ));
+ *
+ * $kernel->context()->name->value();
+ *
+ * // FireHub
+ *
+ * $kernel->context()->environment;
+ *
+ * // FireHub\Application\Context\Environment::PRODUCTION
+ *
+ *
+ * @since 1.0.0
+ *
+ * @return \FireHub\Application\Context Application context.
+ */
+ public function context ():Context {
+
+ return $this->context;
+
+ }
+
+ /**
+ * ### Gets the application dependency injection container
+ * @since 1.0.0
+ *
+ * @return \FireHub\Core\Boundary\Container Application container.
+ */
+ public function container ():ContainerBoundary {
+
+ return $this->container;
+
+ }
+
+ /**
+ * ### Gets the application module manager
+ *
+ * Returns the module manager associated with this application.
+ *
+ * The manager provides access to registered modules and their dependency ordering.
+ * @since 1.0.0
+ *
+ * @return \FireHub\Module\Manager Application module manager.
+ */
+ public function modules ():ModuleManager {
+
+ return $this->modules;
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/Builder.php b/src/Builder.php
new file mode 100644
index 0000000..ef21645
--- /dev/null
+++ b/src/Builder.php
@@ -0,0 +1,505 @@
+
+ * @copyright 2026-present The FireHub Project - All rights reserved
+ * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0
+ *
+ * @php-version >=8.5
+ * @package Application
+ */
+
+namespace FireHub\Application;
+
+use FireHub\Core\Boundary\ {
+ Container\Registrar, Container as ContainerBoundary
+};
+use FireHub\Core\Boundary\ {
+ Module\RegistersServices, Module
+};
+use FireHub\Container\Container;
+use FireHub\Kernel\Kernel;
+use FireHub\Module\Manager as ModuleManager;
+use FireHub\Application\Exception\{
+ ApplicationAlreadyRegisteredException, ApplicationKernelMismatchException,
+ ContainerRegistrationNotSupportedException, InvalidApplicationIdentifierException, UnmanagedApplicationBuilderException
+};
+
+/**
+ * ### Builds and configures FireHub applications
+ *
+ * Provides a fluent API for assembling applications from their context, kernel, dependency injection container,
+ * modules, and lifecycle components.
+ *
+ * The builder supports both standalone and managed application construction. Standalone builders are created using
+ * create(), while managed builders are associated with an application manager and registration identifier.
+ *
+ * Building an application does not boot its kernel, initialize the application, or execute any operations.
+ * Managed builders additionally support registering constructed applications with their associated manager.
+ *
+ * A kernel and container are created automatically when no explicit instances are provided. Managed applications
+ * inherit the shared kernel of their application manager.
+ *
+ * Each build operation creates a new application instance. Explicitly configured kernel and container instances
+ * are shared between builds, while default instances are created independently for standalone applications.
+ *
+ *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Builder;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ * use FireHub\Application\ExecutionContext;
+ * use FireHub\Container\Container;
+ * use FireHub\Kernel\Kernel;
+ *
+ * $kernel = new Kernel;
+ * $container = new Container;
+ *
+ * $container->singleton(MyService::class);
+ * $container->freeze();
+ *
+ * $app = Builder::create(
+ * new Context(
+ * new Name('FireHub'),
+ * Environment::PRODUCTION
+ * ))->withKernel($kernel)
+ * ->withContainer($container)
+ * ->withModule(new AppModule)
+ * ->withInitializer(new AppInitializer)
+ * ->withDisposer(new AppDisposer)
+ * ->build();
+ *
+ * $kernel->boot();
+ * $app->initialize();
+ *
+ * try {
+ *
+ * $app->execute(function (ExecutionContext $execution):void {
+ *
+ * $service = $execution->scope()->get(MyService::class);
+ *
+ * $service->run();
+ *
+ * });
+ *
+ * } finally {
+ *
+ * try {
+ *
+ * $app->dispose();
+ *
+ * } finally {
+ *
+ * $kernel->shutdown();
+ *
+ * }
+ *
+ * }
+ *
+ *
+ * @since 1.0.0
+ */
+final class Builder {
+
+ /**
+ * ### Application manager
+ * @since 1.0.0
+ */
+ private ?Manager $manager = null;
+
+ /**
+ * ### Application registration identifier
+ * @since 1.0.0
+ *
+ * @var null|non-empty-string
+ */
+ private ?string $registration_id = null;
+
+ /**
+ * ### Application kernel
+ * @since 1.0.0
+ */
+ private ?Kernel $kernel = null;
+
+ /**
+ * ### Application dependency injection container
+ * @since 1.0.0
+ */
+ private ?ContainerBoundary $container = null;
+
+ /**
+ * ### Application module manager
+ * @since 1.0.0
+ */
+ private ?ModuleManager $modules = null;
+
+ /**
+ * ### Application initializer
+ * @since 1.0.0
+ *
+ * @var list+ * Application context. + *
+ * + * @return void + */ + private function __construct ( + private readonly Context $context + ) {} + + /** + * ### Creates an application builder + * + * Creates a new builder configured with the specified application context. + * @since 1.0.0 + * + * @param \FireHub\Application\Context $context+ * Application context. + *
+ * + * @return self New application builder. + */ + public static function create (Context $context):self { + + return new self($context); + + } + + /** + * ### Creates a managed application builder + * + * Creates an application builder associated with the specified manager and registration identifier. + * + * Managed applications inherit the manager's shared kernel. + * @since 1.0.0 + * + * @uses \FireHub\Application\Manager::kernel() To get the application kernel. + * + * @param \FireHub\Application\Context $context+ * Application context. + *
+ * @param \FireHub\Application\Manager $manager+ * Application manager. + *
+ * @param non-empty-string $id+ * Application registration identifier. + *
+ * + * @throws \FireHub\Application\Exception\InvalidApplicationIdentifierException If the identifier is empty. + * + * @return self Managed application builder. + */ + public static function managed (Context $context, Manager $manager, string $id):self { + + if ($id === '') + throw new InvalidApplicationIdentifierException( + 'Application identifier cannot be empty.' + ); + + $builder = new self($context); + + $builder->manager = $manager; + $builder->registration_id = $id; + $builder->kernel = $manager->kernel(); + + return $builder; + + } + + /** + * ### Configures the application kernel + * + * Sets the kernel associated with applications created by this builder. + * + * The specified kernel may be shared between multiple applications. + * @since 1.0.0 + * + * @uses \FireHub\Application\Manager::kernel() To get the application kernel. + * + * @param \FireHub\Kernel\Kernel $kernel+ * Application kernel. + *
+ * + * @throws \FireHub\Application\Exception\ApplicationKernelMismatchException If the kernel is not shared with the + * manager. + * + * @return $this Current builder. + */ + public function withKernel (Kernel $kernel):self { + + if ($this->manager !== null && $kernel !== $this->manager->kernel()) + throw new ApplicationKernelMismatchException; + + $this->kernel = $kernel; + + return $this; + + } + + /** + * ### Configures the dependency injection container + * + * Sets the container associated with applications created by this builder. + * + * The container is expected to be configured before application initialization. The builder does not register + * services, freeze registrations, or assume ownership of the container lifecycle. + * @since 1.0.0 + * + * @param \FireHub\Core\Boundary\Container $container+ * Application dependency injection container. + *
+ * + * @return $this Current builder. + */ + public function withContainer (ContainerBoundary $container):self { + + $this->container = $container; + + return $this; + + } + + /** + * ### Registers an application module + * + * Adds a module to the application composition. + * + * Modules are identified by their fully qualified class names. Module dependencies are resolved during + * application construction. + * + * Service registrations contributed by modules are applied during the build process, before application + * initialization. + * @since 1.0.0 + * + * @uses \FireHub\Module\Manager::register() To register the module. + * + * @param \FireHub\Core\Boundary\Module $module+ * Application module. + *
+ * + * @throws \FireHub\Module\Exception\ModuleAlreadyRegisteredException If the module is already registered. + * @throws \FireHub\Module\Exception\ModuleManagerFrozenException If the manager is frozen. + * + * @return $this Current builder. + */ + public function withModule (Module $module):self { + + $this->modules ??= new ModuleManager; + + $this->modules->register($module); + + return $this; + + } + + /** + * ### Registers an application initializer + * + * Adds an initializer to the application startup lifecycle. + * + * Initializers are registered during application construction and executed when the application is initialized. + * @since 1.0.0 + * + * @param \FireHub\Application\Initializer $initializer+ * Initializer to register. + *
+ * @param int $order [optional]+ * Initializer execution order. + *
+ * + * @return $this Current builder. + */ + public function withInitializer (Initializer $initializer, int $order = 0):self { + + $this->initializers[] = [ + 'initializer' => $initializer, + 'order' => $order + ]; + + return $this; + + } + + /** + * ### Registers an application disposer + * + * Adds a disposer to the application shutdown lifecycle. + * + * Disposers are registered during application construction and executed when the application has been disposed. + * @since 1.0.0 + * + * @param Disposer $disposer+ * Disposer to register. + *
+ * @param int $order [optional]+ * Disposer execution order. + *
+ * + * @return $this Current builder. + */ + public function withDisposer (Disposer $disposer, int $order = 0):self { + + $this->disposers[] = [ + 'disposer' => $disposer, + 'order' => $order + ]; + + return $this; + + } + + /** + * ### Builds an application + * + * Creates an application using the configured context, kernel, dependency injection container, modules, + * initializers, and disposers. + * + * Modules are resolved in dependency order, and their service registrations are applied before the application + * is constructed. + * + * The module manager is cloned and frozen after successful service registration, preventing further changes + * to the application's module composition. + * + * Containers created by the builder are automatically frozen after service registration. Externally supplied + * containers remain under the caller's control and are never frozen by the builder. + * + * Building an application does not initialize its lifecycle. + * @since 1.0.0 + * + * @uses \FireHub\Module\Manager::ordered() To get the ordered module list. + * @uses \FireHub\Module\Manager::freeze() To freeze the module registry. + * @uses \FireHub\Core\Boundary\Module\RegistersServices::register() To register services. + * @uses \FireHub\Container\Container::freeze() To freeze the container. + * @uses \FireHub\Application\Application::registerInitializer() To register application initializers. + * @uses \FireHub\Application\Application::registerDisposer() To register application disposers. + * + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If registration is not allowed. + * @throws \FireHub\Module\Exception\ModuleDependencyNotFoundException If a required module is not registered. + * @throws \FireHub\Module\Exception\CircularModuleDependencyException If circular dependencies are detected. + * @throws \FireHub\Application\Exception\ContainerRegistrationNotSupportedException If the container does not + * support service registration. + * + * @return \FireHub\Application\Application Configured application. + */ + public function build ():Application { + + $container = $this->container ?? new Container; + + $modules = clone ($this->modules ?? new ModuleManager); + + foreach ($modules->ordered() as $module) { + + if (!$module instanceof RegistersServices) + continue; + + if (!$container instanceof Registrar) + throw new ContainerRegistrationNotSupportedException( + 'The configured container does not support service registration.' + ); + + $module->register($container); + + } + + $modules->freeze(); + + if ($this->container === null && $container instanceof Container) + $container->freeze(); + + $application = new Application( + $this->context, + $this->kernel ?? new Kernel, + $container, + $modules + ); + + foreach ($this->initializers as $entry) + $application->registerInitializer( + $entry['initializer'], + $entry['order'] + ); + + foreach ($this->disposers as $entry) + $application->registerDisposer( + $entry['disposer'], + $entry['order'] + ); + + return $application; + + } + + /** + * ### Builds and registers an application + * + * Constructs the application and registers it with the associated application manager. + * + * Registration is available only for builders created through Builder::managed(). + * @since 1.0.0 + * + * @uses \FireHub\Application\Builder::build() To construct the application. + * @uses \FireHub\Application\Manager::register() To register the application. + * @uses \FireHub\Application\Manager::has() To check if the application is already registered. + * + * @throws \FireHub\Application\Exception\UnmanagedApplicationBuilderException If the builder is not associated + * with a manager. + * @throws \FireHub\Application\Exception\ApplicationAlreadyRegisteredException If the identifier is already + * registered. + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If registration is not allowed. + * @throws \FireHub\Module\Exception\ModuleDependencyNotFoundException If a required module is not registered. + * @throws \FireHub\Module\Exception\CircularModuleDependencyException If circular dependencies are detected. + * @throws \FireHub\Application\Exception\ContainerRegistrationNotSupportedException If the container does not + * support service registration. + * @throws \FireHub\Application\Exception\InvalidApplicationIdentifierException If the identifier is empty. + * @throws \FireHub\Application\Exception\ApplicationAlreadyRegisteredException If the application is already + * registered. + * @throws \FireHub\Application\Exception\ApplicationKernelMismatchException If the application kernel does not + * match the manager kernel. + * + * @return \FireHub\Application\Application Registered application. + */ + public function register ():Application { + + if ($this->manager === null || $this->registration_id === null) + throw new UnmanagedApplicationBuilderException( + 'Cannot register an application using a standalone builder.', + context: [ + 'manager' => $this->manager, + 'registration_id' => $this->registration_id, + ] + ); + + if ($this->manager->has($this->registration_id)) + throw new ApplicationAlreadyRegisteredException; + + $application = $this->build(); + + $this->manager->register( + $this->registration_id, + $application + ); + + return $application; + + } + +} \ No newline at end of file diff --git a/src/Context.php b/src/Context.php new file mode 100644 index 0000000..1e69f23 --- /dev/null +++ b/src/Context.php @@ -0,0 +1,89 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.2 + * @package Application + */ + +namespace FireHub\Application; + +use FireHub\Core\Type\ValueObject; +use FireHub\Application\Context\ { + Environment, Name +}; + +/** + * ### Represents the application execution context + * + * Defines the immutable identity and execution environment of an application. + * + * The context contains application-wide metadata and remains unchanged throughout the application lifecycle. + * @since 1.0.0 + * + * @extends \FireHub\Core\Type\ValueObject+ * Application name. + *
+ * @param \FireHub\Application\Context\Environment $environment+ * Application environment. + *
+ * + * @return void + */ + public function __construct ( + public Name $name, + public Environment $environment + ) {} + + /** + * @inheritDoc + * + * @since 1.0.0 + */ + public function value ():array { + + return [ + 'name' => $this->name, + 'environment' => $this->environment + ]; + + } + + /** + * @inheritDoc + * + * @since 1.0.0 + * + * @uses \FireHub\Application\Context\Name::value() To get the name value. + * + * @return array{ + * name: non-empty-string, + * environment: \FireHub\Application\Context\Environment + * } Comparison value. + */ + protected function comparisonValue ():array { + + return [ + 'name' => $this->name->value(), + 'environment' => $this->environment + ]; + + } + +} \ No newline at end of file diff --git a/src/Context/Environment.php b/src/Context/Environment.php new file mode 100644 index 0000000..82859aa --- /dev/null +++ b/src/Context/Environment.php @@ -0,0 +1,59 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Context; + +/** + * ### Defines the application execution environment + * + * Represents the environment in which a FireHub application is executed. + * + * Application environments allow the framework and its components to distinguish between production, staging, + * development, and testing configurations without relying on arbitrary string values. + * @since 1.0.0 + */ +enum Environment { + + /** + * ### Production environment + * + * Represents a live application environment intended for end users. + * @since 1.0.0 + */ + case PRODUCTION; + + /** + * ### Staging environment + * + * Represents a pre-production environment used for deployment verification and acceptance testing. + * @since 1.0.0 + */ + case STAGING ; + + /** + * ### Development environment + * + * Represents an environment used for application development, debugging, and local execution. + * @since 1.0.0 + */ + case DEVELOPMENT; + + /** + * ### Testing environment + * + * Represents an environment used for automated tests and application behavior verification. + * @since 1.0.0 + */ + case TESTING; + +} \ No newline at end of file diff --git a/src/Context/Name.php b/src/Context/Name.php new file mode 100644 index 0000000..734e5ad --- /dev/null +++ b/src/Context/Name.php @@ -0,0 +1,71 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.2 + * @package Application + */ + +namespace FireHub\Application\Context; + +use FireHub\Core\Type\Str\Encoding; +use FireHub\Foundation\Identifier\Name as IdentifierName; +use FireHub\Foundation\Identifier\Exception\InvalidIdentifierValueException; +use FireHub\Runtime; + +/** + * ### Represents an application name + * + * Defines an immutable, strongly typed name used to identify an application within the FireHub ecosystem. + * + * The application name inherits its fundamental validation rules and value semantics from the Foundation identifier + * name implementation. + * + * Application names are compared by their concrete type and underlying value, ensuring that they remain distinct + * from other identifier types. + * @since 1.0.0 + * + * @template TValue of non-empty-string + * + * @extends \FireHub\Foundation\Identifier\Name+ * The identifier name. + *
+ * + * @throws \FireHub\Foundation\Identifier\Exception\InvalidIdentifierValueException If the identifier name is + * empty or does not meet the length requirements. + * @throws \FireHub\Core\Exception\FireHubException If the condition is not met. + * @throws \FireHub\Core\Type\Exception\ValueObjectException If the exception is not a FireHubException. + * + * @return void + */ + public function __construct (string $value) { + + parent::__construct($value); + + $length = Runtime\Str\MB\Inspection::length($value, Encoding::UTF_8); + + $this->guard( + fn() => $length >= 3 && $length <= 64, + fn() => new InvalidIdentifierValueException( + 'Application name must contain between 3 and 64 characters.' + ) + ); + + } + +} \ No newline at end of file diff --git a/src/Disposer.php b/src/Disposer.php new file mode 100644 index 0000000..854d994 --- /dev/null +++ b/src/Disposer.php @@ -0,0 +1,49 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=7.4 + * @package Application + */ + +namespace FireHub\Application; + +/** + * ### Defines an application disposer + * + * Establishes the contract for releasing application-specific resources and performing cleanup during + * the application shutdown lifecycle. + * + * Disposers are executed during the application shutdown in their configured execution order. Each disposer + * receives the application instance, allowing access to its context and application-managed components. + * + * Disposal operations should release resources safely and should not depend on the application remaining + * operational after shutdown completes. + * + * This contract is independent of the kernel terminator contract and does not participate directly in + * the kernel lifecycle. + * @since 1.0.0 + */ +interface Disposer { + + /** + * ### Disposes of an application component + * + * Performs application-specific cleanup, releases allocated resources, and completes any required + * finalization operations. + * @since 1.0.0 + * + * @param \FireHub\Application\Application $application+ * Application being shut down. + *
+ * + * @return void + */ + public function dispose (Application $application):void; + +} \ No newline at end of file diff --git a/src/Exception/ApplicationAlreadyRegisteredException.php b/src/Exception/ApplicationAlreadyRegisteredException.php new file mode 100644 index 0000000..f9d9a4e --- /dev/null +++ b/src/Exception/ApplicationAlreadyRegisteredException.php @@ -0,0 +1,29 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\RuntimeException; + +/** + * ### Application already registered exception + * + * Thrown when attempting to register an application using an identifier that is already associated with another + * application. + * @since 1.0.0 + */ +final class ApplicationAlreadyRegisteredException extends RuntimeException { + + protected const string DEFAULT_MESSAGE = 'The application identifier is already registered'; + +} \ No newline at end of file diff --git a/src/Exception/ApplicationDisposalException.php b/src/Exception/ApplicationDisposalException.php new file mode 100644 index 0000000..e9184ed --- /dev/null +++ b/src/Exception/ApplicationDisposalException.php @@ -0,0 +1,32 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\RuntimeException; + +/** + * ### Represents an application disposal failure + * + * Indicates that one or more application disposers failed while releasing managed resources. + * + * Remaining disposers are attempted even when an individual disposer fails. + * + * The original failure is preserved as the previous exception. + * @since 1.0.0 + */ +final class ApplicationDisposalException extends RuntimeException { + + protected const string DEFAULT_MESSAGE = 'The application failed to dispose'; + +} \ No newline at end of file diff --git a/src/Exception/ApplicationInitializationException.php b/src/Exception/ApplicationInitializationException.php new file mode 100644 index 0000000..324cb94 --- /dev/null +++ b/src/Exception/ApplicationInitializationException.php @@ -0,0 +1,32 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\RuntimeException; + +/** + * ### Represents an application disposal failure + * + * Indicates that one or more application disposers failed while releasing managed resources. + * + * Remaining disposers are attempted even when an individual disposer fails. + * + * The original failure is preserved as the previous exception. + * @since 1.0.0 + */ +final class ApplicationInitializationException extends RuntimeException { + + protected const string DEFAULT_MESSAGE = 'The application failed to initialize'; + +} \ No newline at end of file diff --git a/src/Exception/ApplicationKernelMismatchException.php b/src/Exception/ApplicationKernelMismatchException.php new file mode 100644 index 0000000..b92c6e8 --- /dev/null +++ b/src/Exception/ApplicationKernelMismatchException.php @@ -0,0 +1,29 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\Runtime\InvalidArgumentException; + +/** + * ### Application kernel mismatch exception + * + * Thrown when attempting to register an application whose kernel differs from the kernel managed by the application + * manager. + * @since 1.0.0 + */ +final class ApplicationKernelMismatchException extends InvalidArgumentException { + + protected const string DEFAULT_MESSAGE = 'The application kernel does not match the application manager kernel'; + +} \ No newline at end of file diff --git a/src/Exception/ApplicationNotFoundException.php b/src/Exception/ApplicationNotFoundException.php new file mode 100644 index 0000000..1a630bf --- /dev/null +++ b/src/Exception/ApplicationNotFoundException.php @@ -0,0 +1,28 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\Runtime\OutOfBoundsException; + +/** + * ### Application was not found exception + * + * Thrown when attempting to retrieve an application not registered with the application manager. + * @since 1.0.0 + */ +final class ApplicationNotFoundException extends OutOfBoundsException { + + protected const string DEFAULT_MESSAGE = 'The application was not found'; + +} \ No newline at end of file diff --git a/src/Exception/ContainerRegistrationNotSupportedException.php b/src/Exception/ContainerRegistrationNotSupportedException.php new file mode 100644 index 0000000..38339b4 --- /dev/null +++ b/src/Exception/ContainerRegistrationNotSupportedException.php @@ -0,0 +1,29 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\RuntimeException; + +/** + * ### Container registration not a supported exception + * + * Thrown when application composition requires service registration, but the configured dependency injection + * container does not implement the required registration boundary. + * @since 1.0.0 + */ +final class ContainerRegistrationNotSupportedException extends RuntimeException { + + protected const string DEFAULT_MESSAGE = 'The configured dependency injection container does not support service registration'; + +} \ No newline at end of file diff --git a/src/Exception/InvalidApplicationIdentifierException.php b/src/Exception/InvalidApplicationIdentifierException.php new file mode 100644 index 0000000..b15af2f --- /dev/null +++ b/src/Exception/InvalidApplicationIdentifierException.php @@ -0,0 +1,28 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\Runtime\InvalidArgumentException; + +/** + * ### Invalid application identifier exception + * + * Thrown when an application identifier does not satisfy the manager's registration requirements. + * @since 1.0.0 + */ +final class InvalidApplicationIdentifierException extends InvalidArgumentException { + + protected const string DEFAULT_MESSAGE = 'The application identifier is invalid'; + +} \ No newline at end of file diff --git a/src/Exception/InvalidApplicationStateException.php b/src/Exception/InvalidApplicationStateException.php new file mode 100644 index 0000000..e4f6c7b --- /dev/null +++ b/src/Exception/InvalidApplicationStateException.php @@ -0,0 +1,32 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\Runtime\KernelException; + +/** + * ### Represents an application initialization failure + * + * Indicates that one or more application initializers could not complete successfully. + * + * The original failure is preserved as the previous exception, allowing callers to inspect the underlying cause. + * + * An initialization failure prevents the application from entering its initialized state. + * @since 1.0.0 + */ +final class InvalidApplicationStateException extends KernelException { + + protected const string DEFAULT_MESSAGE = 'The application is in an invalid state'; + +} \ No newline at end of file diff --git a/src/Exception/UnmanagedApplicationBuilderException.php b/src/Exception/UnmanagedApplicationBuilderException.php new file mode 100644 index 0000000..0e7ac82 --- /dev/null +++ b/src/Exception/UnmanagedApplicationBuilderException.php @@ -0,0 +1,31 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application\Exception; + +use FireHub\Core\Exception\Runtime\KernelException; + +/** + * ### Indicates that an application builder is not managed + * + * Thrown when an operation requiring an associated application manager is attempted to use a standalone application + * builder. + * + * Standalone builders can construct applications but cannot register them without an associated application manager. + * @since 1.0.0 + */ +final class UnmanagedApplicationBuilderException extends KernelException { + + protected const string DEFAULT_MESSAGE = 'The application builder is not managed'; + +} \ No newline at end of file diff --git a/src/ExecutionContext.php b/src/ExecutionContext.php new file mode 100644 index 0000000..4d189a9 --- /dev/null +++ b/src/ExecutionContext.php @@ -0,0 +1,80 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.5 + * @package Application + */ + +namespace FireHub\Application; + +use FireHub\Core\Boundary\Container\Scope; + +/** + * ### Represents an application execution context + * + * Provides access to the application and its isolated dependency injection scope during a single execution. + * + * Each execution context is associated with exactly one scope. + * + * Scoped services are shared within the same execution context but remain isolated from other execution contexts. + * + * The application owns the scope lifecycle. The scope is automatically closed when execution completes. + * @since 1.0.0 + */ +final readonly class ExecutionContext { + + /** + * ### Constructor + * @since 1.0.0 + * + * @param \FireHub\Application\Application $application+ * Application associated with this execution. + *
+ * @param \FireHub\Core\Boundary\Container\Scope $scope+ * Execution dependency injection scope. + *
+ * + * @return void + */ + public function __construct ( + private Application $application, + private Scope $scope + ) {} + + /** + * ### Gets the application + * + * Returns the application associated with this execution context. + * @since 1.0.0 + * + * @return \FireHub\Application\Application Application instance. + */ + public function application ():Application { + + return $this->application; + + } + + /** + * ### Gets the execution dependency injection scope + * + * Returns the isolated scope associated with this execution. + * + * The scope is managed by the application and must not be manually closed by consumers. + * @since 1.0.0 + * + * @return \FireHub\Core\Boundary\Container\Scope Execution scope. + */ + public function scope ():Scope { + + return $this->scope; + + } + +} \ No newline at end of file diff --git a/src/Initializer.php b/src/Initializer.php new file mode 100644 index 0000000..b00ed71 --- /dev/null +++ b/src/Initializer.php @@ -0,0 +1,47 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=7.4 + * @package Application + */ + +namespace FireHub\Application; + +/** + * ### Defines an application initializer + * + * Establishes the contract for initializing application-specific components during the application startup + * lifecycle. + * + * Initializers are executed during application boot in their configured execution order. Each initializer + * receives the application instance, allowing access to its context and application-managed components. + * + * Initialization must complete successfully before later initializers are executed. + * + * This contract is independent of the kernel bootstrapper contract and does not participate directly in + * the kernel lifecycle. + * @since 1.0.0 + */ +interface Initializer { + + /** + * ### Initializes an application component + * + * Performs application-specific initialization and prepares the component for application execution. + * @since 1.0.0 + * + * @param \FireHub\Application\Application $application+ * Application being initialized. + *
+ * + * @return void + */ + public function initialize (Application $application):void; + +} \ No newline at end of file diff --git a/src/Manager.php b/src/Manager.php new file mode 100644 index 0000000..5b18fdf --- /dev/null +++ b/src/Manager.php @@ -0,0 +1,412 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.5 + * @package Application + */ + +namespace FireHub\Application; + +use FireHub\Kernel\Kernel; +use FireHub\Container\Container; +use FireHub\Application\Exception\ { + ApplicationAlreadyRegisteredException, ApplicationKernelMismatchException, ApplicationNotFoundException, + InvalidApplicationIdentifierException +}; +use FireHub\Runtime; + +/** + * ### Manages multiple FireHub applications + * + * Coordinates the registration, initialization, execution lifecycle, and disposal of multiple applications + * associated with a shared kernel. + * + * Applications retain independent lifecycle states and dependency injection containers while sharing the same + * kernel instance. The manager coordinates application lifecycle ordering without managing individual executions + * or dependency injection scopes. + * + * Applications can be registered explicitly using register() or configured and registered through managed builders + * created using application(). + * + * The bootstrap() operation boots the shared kernel and initializes all registered applications in registration + * order. Applications are disposed in reverse registration order using disposeAll(). + * + * The shared kernel remains active until shutdown() is explicitly invoked. + * + *
+ * use FireHub\Application\Application;
+ * use FireHub\Application\Context;
+ * use FireHub\Application\Context\Name;
+ * use FireHub\Application\Context\Environment;
+ * use FireHub\Application\ExecutionContext;
+ * use FireHub\Application\Manager;
+ *
+ * $manager = new Manager;
+ *
+ * $manager->application(
+ * 'frontend',
+ * new Context(
+ * new Name('Frontend'),
+ * Environment::PRODUCTION
+ * ))->withModule(new FrontendModule)
+ * ->withInitializer(new FrontendInitializer)
+ * ->withDisposer(new FrontendDisposer)
+ * ->register();
+ *
+ * $manager->application(
+ * 'backend',
+ * new Context(
+ * new Name('Backend'),
+ * Environment::PRODUCTION
+ * ))->withModule(new BackendModule)
+ * ->register();
+ *
+ * try {
+ *
+ * $manager->bootstrap();
+ *
+ * $manager->get('frontend')->execute(
+ * function (ExecutionContext $execution):void {
+ *
+ * $service = $execution->scope()->get(MyService::class);
+ *
+ * $service->run();
+ *
+ * }
+ * );
+ *
+ * } finally {
+ *
+ * $manager->shutdown();
+ *
+ * }
+ *
+ *
+ * @since 1.0.0
+ */
+final class Manager {
+
+ /**
+ * ### Registered applications
+ * @since 1.0.0
+ *
+ * @var array+ * Shared application kernel. + *
+ * + * @return void + */ + public function __construct ( + private readonly Kernel $kernel = new Kernel + ) {} + + /** + * ### Creates an application builder + * + * Creates a new application builder configured with the manager's shared kernel. + * + * The builder allows additional application configuration before construction. + * + * Building the application does not automatically register it with the manager. + * @since 1.0.0 + * + * @uses \FireHub\Application\Builder::create() To create the application builder. + * @uses \FireHub\Application\Builder::withKernel() To configure the shared kernel. + * + * @param \FireHub\Application\Context $context+ * Application context. + *
+ * + * @throws \FireHub\Application\Exception\ApplicationKernelMismatchException If the kernel is not shared with the + * manager. + * + * @return \FireHub\Application\Builder Configured application builder. + */ + public function create (Context $context):Builder { + + return Builder::create($context)->withKernel($this->kernel); + + } + + /** + * ### Creates a managed application builder + * + * Creates a builder associated with this manager and assigns the specified registration identifier. + * + * The resulting builder can register the constructed application directly with this manager. + * @since 1.0.0 + * + * @uses \FireHub\Application\Builder::managed() To create the application builder. + * + * @param non-empty-string $id+ * Application registration identifier. + *
+ * @param \FireHub\Application\Context $context+ * Application context. + *
+ * + * @throws \FireHub\Application\Exception\InvalidApplicationIdentifierException If the identifier is empty. + * + * @return \FireHub\Application\Builder Managed application builder. + */ + public function application (string $id, Context $context):Builder { + + return Builder::managed($context, $this, $id); + + } + + /** + * ### Registers an application + * + * Registers an application under the specified identifier. + * + * The application must use the same kernel as this manager. + * @since 1.0.0 + * + * @uses \FireHub\Application\Application::kernel() To get the application kernel. + * + * @param non-empty-string $id+ * Unique application identifier. + *
+ * @param \FireHub\Application\Application $application+ * Application to register. + *
+ * + * @throws \FireHub\Application\Exception\InvalidApplicationIdentifierException If the identifier is empty. + * @throws \FireHub\Application\Exception\ApplicationAlreadyRegisteredException If the application is already + * registered. + * @throws \FireHub\Application\Exception\ApplicationKernelMismatchException If the application kernel does not + * match the manager kernel. + * + * @return $this Current manager. + */ + public function register (string $id, Application $application):self { + + if ($id === '') + throw new InvalidApplicationIdentifierException( + 'Application identifier cannot be empty.' + ); + + if (isset($this->applications[$id])) + throw new ApplicationAlreadyRegisteredException; + + if ($application->kernel() !== $this->kernel) + throw new ApplicationKernelMismatchException( + context: [ + 'application_kernel' => $application->kernel(), + 'kernel' => $this->kernel + ] + ); + + $this->applications[$id] = $application; + + return $this; + + } + + /** + * ### Gets a registered application + * @since 1.0.0 + * + * @param non-empty-string $id+ * Application identifier. + *
+ * + * @throws \FireHub\Application\Exception\ApplicationNotFoundException If the application is not registered. + * + * @return \FireHub\Application\Application Registered application. + */ + public function get (string $id):Application { + + return $this->applications[$id] + ?? throw new ApplicationNotFoundException; + + } + + /** + * ### Checks whether an application is registered + * @since 1.0.0 + * + * @param non-empty-string $id+ * Application identifier. + *
+ * + * @return bool True if the application is registered. + */ + public function has (string $id):bool { + + return isset($this->applications[$id]); + + } + + /** + * ### Gets all registered applications + * @since 1.0.0 + * + * @return array+ * Application identifier. + *
+ * + * @throws \FireHub\Application\Exception\ApplicationNotFoundException If the application is not registered. + * @throws \FireHub\Kernel\Exception\InvalidKernelStateException If booting is not allowed. + * @throws \FireHub\Kernel\Exception\KernelBootException If initialization fails. + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If initialization is not allowed. + * @throws \FireHub\Application\Exception\ApplicationInitializationException If an initializer fails. + * + * @return void + */ + public function initialize (string $id):void { + + $application = $this->get($id); + + $this->kernel->boot(); + + $application->initialize(); + + } + + /** + * ### Disposes a registered application + * + * Disposes the specified application without shutting down the shared kernel. + * @since 1.0.0 + * + * @uses \FireHub\Application\Manager::get() To get the application. + * @uses \FireHub\Application\Application::dispose() To dispose the application. + * + * @param non-empty-string $id+ * Application identifier. + *
+ * + * @throws \FireHub\Application\Exception\ApplicationNotFoundException If the application is not registered. + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If disposal is not allowed. + * @throws \FireHub\Application\Exception\ApplicationDisposalException If a disposer fails. + * + * @return void + */ + public function dispose (string $id):void { + + $this->get($id)->dispose(); + + } + + /** + * ### Bootstraps all registered applications + * + * Boots the shared kernel and initializes all registered applications in registration order. + * + * Each application retains its independent lifecycle state while sharing the same kernel. + * @since 1.0.0 + * + * @uses \FireHub\Kernel\Kernel::boot() To boot the kernel. + * @uses \FireHub\Application\Application::initialize() To initialize the application. + * @uses \FireHub\Application\Application::container() To get the application container. + * @uses \FireHub\Container\Container::freeze() To freeze the container. + * + * @throws \FireHub\Kernel\Exception\InvalidKernelStateException If booting is not allowed. + * @throws \FireHub\Kernel\Exception\KernelBootException If initialization fails. + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If initialization is not allowed. + * @throws \FireHub\Application\Exception\ApplicationInitializationException If an initializer fails. + * + * @return void + */ + public function bootstrap ():void { + + foreach ($this->applications as $application) { + + $container = $application->container(); + + if ($container instanceof Container) + $container->freeze(); + + } + + $this->kernel->boot(); + + foreach ($this->applications as $application) + $application->initialize(); + + } + + /** + * ### Shuts down all managed applications and the shared kernel + * + * Disposes all registered applications in reverse registration order and subsequently shuts down the shared kernel. + * + * The kernel shutdown is attempted even if application disposal fails. + * + * Application disposal must be complete before the kernel is shut down because applications may depend on kernel + * services during their disposal lifecycle. + * @since 1.0.0 + * + * @uses \FireHub\Application\Application::dispose() To dispose the application. + * @uses \FireHub\Runtime\Arr\Transform::reverse() To reverse the application registration order. + * @uses \FireHub\Kernel\Kernel::shutdown() To shutdown the kernel. + * + * @throws \FireHub\Kernel\Exception\InvalidKernelStateException If shutdown is not allowed. + * @throws \FireHub\Kernel\Exception\KernelShutdownException If termination fails. + * @throws \FireHub\Application\Exception\InvalidApplicationStateException If disposal is not allowed. + * @throws \FireHub\Application\Exception\ApplicationDisposalException If a disposer fails. + * + * @return void + */ + public function shutdown ():void { + + try { + + foreach (Runtime\Arr\Transform::reverse($this->applications) as $application) + $application->dispose(); + + } finally { + + $this->kernel->shutdown(); + + } + + } + +} \ No newline at end of file diff --git a/src/State.php b/src/State.php new file mode 100644 index 0000000..471404f --- /dev/null +++ b/src/State.php @@ -0,0 +1,84 @@ + + * @copyright 2026-present The FireHub Project - All rights reserved + * @license https://opensource.org/license/Apache-2-0 Apache License, Version 2.0 + * + * @php-version >=8.1 + * @package Application + */ + +namespace FireHub\Application; + +/** + * ### Defines application lifecycle states + * + * Represents the possible states of an application throughout its initialization, execution readiness, + * and resource disposal lifecycle. + * + * Application lifecycle states are independent of kernel lifecycle states and reflect the execution + * of application initializers and disposers. + * @since 1.0.0 + */ +enum State { + + /** + * ### Created + * + * The application has been instantiated but has not started initialization. + * @since 1.0.0 + */ + case CREATED; + + /** + * ### Initializing + * + * The application is currently executing its initializers and preparing for execution. + * @since 1.0.0 + */ + case INITIALIZING; + + /** + * ### Initialized + * + * The application has successfully completed initialization and is ready for execution. + * @since 1.0.0 + */ + case INITIALIZED; + + /** + * ### Disposing + * + * The application is currently executing its disposers and releasing managed resources. + * @since 1.0.0 + */ + case DISPOSING; + + /** + * ### Disposed + * + * The application has completed its disposal process and is no longer operational. + * @since 1.0.0 + */ + case DISPOSED; + + /** + * ### Initialization Failed + * + * The application failed to execute its initializers and cannot continue its normal lifecycle. + * @since 1.0.0 + */ + case INITIALIZATION_FAILED; + + /** + * ### Disposal Failed + * + * The application failed to execute its disposers and cannot complete its normal lifecycle. + * @since 1.0.0 + */ + case DISPOSAL_FAILED; + +} \ No newline at end of file