Application Lifecycle
Understanding the Orkestra application lifecycle is essential for properly leveraging the framework's capabilities. This guide covers the bootstrapping process, various lifecycle stages, and how to hook into these stages in your application.
Overview
The Orkestra application lifecycle consists of several distinct phases:
- Initialization: The
App
instance is created - Configuration: Application settings are loaded and validated
- Provider Registration: Service providers are registered
- Provider Bootstrapping: Service providers are booted in sequence
- Execution: The application handles the request and generates a response
- Termination: Resources are released and the application terminates
Initialization Phase
The application begins with the creation of an App
instance, which serves as the central container for all services:
use Orkestra\App;
use Orkestra\Configuration;
// Create a new app instance
$app = new App(new Configuration());
Skeleton Repository Bootstrap
If you're using the Orkestra skeleton repository, the initialization is handled for you in the bootstrap/app.php
file:
// bootstrap/app.php
$app = new \Orkestra\App(new \Orkestra\Configuration(require __DIR__ . '/../config/app.php'));
// Register additional configurations
$app->config()->set('custom_config', require __DIR__ . '/../config/custom.php');
// This helper is only available in the skeleton repository
function app(): \Orkestra\App
{
global $app;
return $app;
}
return $app;
Note: The
app()
function is only available in the skeleton repository and should not be relied upon in core Orkestra applications. Always use the$app
instance directly.
Hooking Into Initialization
To customize the initialization phase, you can modify the bootstrap file or create your own bootstrap process:
// Create a configuration with custom values
$config = new Configuration([
'env' => $_ENV['APP_ENV'] ?? 'development',
'root' => __DIR__,
'slug' => 'my-app'
]);
// Create the app with custom configuration
$app = new App($config);
// Store app instance globally if needed
$GLOBALS['app'] = $app;
Configuration Phase
During this phase, the application loads and validates configuration values. In the skeleton repository, configuration is typically defined in the config/app.php
file:
// config/app.php
return [
'env' => $_ENV['APP_ENV'] ?? 'development',
'root' => dirname(__DIR__),
'slug' => 'my-app',
'providers' => [
// List of service providers
\Orkestra\Providers\CommandsProvider::class,
\Orkestra\Providers\HooksProvider::class,
\App\Providers\AppServiceProvider::class,
],
// Other configuration values
];
You can set configuration values manually:
// Set configuration values
$app->config()->set('env', 'development');
$app->config()->set('root', './');
$app->config()->set('slug', 'my-app');
// Configuration values can be validated
$app->config()->validate();
Hooking Into Configuration
To add custom configuration logic:
- Create additional configuration files:
// config/database.php
return [
'driver' => $_ENV['DB_DRIVER'] ?? 'mysql',
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'port' => (int)($_ENV['DB_PORT'] ?? 3306),
'name' => $_ENV['DB_NAME'],
'user' => $_ENV['DB_USER'],
'password' => $_ENV['DB_PASSWORD'],
];
// In bootstrap/app.php
$app->config()->set('database', require __DIR__ . '/../config/database.php');
- Add configuration validation:
// In a service provider's register method
public function register(App $app): void
{
$app->config()->set('validation', [
'database' => function ($value) {
// Validate database configuration
return isset($value['host']) && isset($value['name']);
}
]);
}
Configuration Validation
Orkestra validates configuration values against defined rules:
// Define configuration schema
$config = new Configuration([
'definition' => [
'key1' => ['Description of key1', 'default1'],
'key2' => ['Description of key2', null], // Required value (null default)
],
'validation' => [
'key1' => fn ($value) => $value === 'validValue',
],
]);
// Set and validate
$config->set('key1', 'validValue');
$config->set('key2', 'someValue');
$config->validate(); // Will throw exception if validation fails
Provider Registration Phase
Service providers are registered with the application. In the skeleton repository, providers are typically listed in the config/app.php
file:
// config/app.php
return [
'providers' => [
\Orkestra\Providers\CommandsProvider::class,
\Orkestra\Providers\HooksProvider::class,
\Orkestra\Providers\HttpProvider::class,
\Orkestra\Providers\ViewProvider::class,
\App\Providers\AppServiceProvider::class,
],
];
You can also register providers manually:
// Register providers
$app->provider(MyServiceProvider::class);
$app->provider(AnotherServiceProvider::class);
Service providers must implement the ProviderInterface
:
use Orkestra\App;
use Orkestra\Interfaces\ProviderInterface;
class MyServiceProvider implements ProviderInterface
{
public function register(App $app): void
{
// Register services in container
$app->bind(MyService::class, fn() => new MyService());
}
public function boot(App $app): void
{
// Initialize services, run setup tasks
$app->get(MyService::class)->initialize();
}
}
Hooking Into Provider Registration
To customize provider registration:
- Create custom service providers:
namespace App\Providers;
use Orkestra\App;
use Orkestra\Interfaces\ProviderInterface;
class CustomProvider implements ProviderInterface
{
public function register(App $app): void
{
// Registration logic
}
public function boot(App $app): void
{
// Boot logic
}
}
// Add to config/app.php's providers array
// Or register manually in bootstrap/app.php
$app->provider(\App\Providers\CustomProvider::class);
- Extend existing providers:
namespace App\Providers;
use Orkestra\Providers\HttpProvider;
class ExtendedHttpProvider extends HttpProvider
{
public function register(App $app): void
{
parent::register($app);
// Additional registration logic
}
public function boot(App $app): void
{
parent::boot($app);
// Additional boot logic
}
}
// In config/app.php, replace HttpProvider with ExtendedHttpProvider
Provider Bootstrapping Phase
After registration, providers are booted in sequence. In the skeleton repository, this is typically handled in the public entry point:
// public/index.php
$app = require_once __DIR__ . '/../bootstrap/app.php';
// Boot the application
$app->boot();
// Handle the request
$kernel = $app->get(\Orkestra\Services\Http\Kernel::class);
$response = $kernel->handle($request);
$response->send();
You can also boot the application manually:
// Initialize all registered providers
$app->boot();
During this phase:
- The application validates the environment, root path, and slug
- Each provider's
register()
method is called on all providers first - Then each provider's
boot()
method is called in the order they were registered - Service dependencies are resolved and ready to use
Hooking Into Provider Bootstrapping
The primary way to hook into bootstrapping is through provider boot()
methods:
public function boot(App $app): void
{
// Access configuration
$debug = $app->config()->get('app.debug');
// Use hooks (if HooksProvider is available)
$hooks = $app->get(\Orkestra\Services\Hooks\Interfaces\HooksInterface::class);
$hooks->addListener('application.booted', function() {
// Run after all providers are booted
});
// Initialize services
$myService = $app->get(MyService::class);
$myService->initialize();
}
Execution Phase
Once booted, the application handles requests through the appropriate channels (HTTP, CLI, etc.). In the skeleton repository, this is typically handled in the entry point files:
// For HTTP requests (public/index.php)
$kernel = $app->get(\Orkestra\Services\Http\Kernel::class);
$response = $kernel->handle($request);
$response->send();
// For CLI commands (maestro)
$runner = $app->get(\Orkestra\Services\Commands\Runner::class);
$runner->run();
Hooking Into Execution
To customize the execution phase:
- Create custom controllers or command handlers:
namespace App\Controllers;
use Orkestra\Services\Http\Controllers\AbstractController;
class HomeController extends AbstractController
{
public function index()
{
return $this->view('home', ['title' => 'Welcome']);
}
}
// Register in routes (config/routes.php)
return [
'GET /' => 'App\Controllers\HomeController@index'
];
- Use middleware (if HttpProvider is used):
namespace App\Middleware;
use Closure;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class AuthMiddleware
{
public function handle(ServerRequestInterface $request, Closure $next): ResponseInterface
{
// Add middleware logic
if (!$this->isAuthenticated($request)) {
return redirect('/login');
}
return $next($request);
}
}
// Register in routes or providers
Service Container Usage During Lifecycle
The container is the backbone of the application lifecycle:
// Register services during provider registration
public function register(App $app): void
{
$app->bind(MyInterface::class, MyImplementation::class);
// Sophisticated bindings
$app->bind(ComplexService::class, function() {
$instance = new ComplexService();
$instance->setLogger(new Logger());
return $instance;
});
}
// Use services during the boot phase
public function boot(App $app): void
{
$service = $app->get(MyInterface::class);
$service->doSomething();
}
Important Considerations
Boot Only Once
An application can only be booted once. Attempting to boot multiple times will throw an exception:
$app->boot();
$app->boot(); // Throws Exception
Service Access Before Boot
You cannot access services from the container before the application is booted:
$app = new App(new Configuration());
$app->get('service'); // Throws BadMethodCallException
// Correct approach
$app->boot();
$app->get('service'); // Works after booting
Configuration Validation
Invalid configurations will throw exceptions during boot:
// Invalid environment
$app->config()->set('env', 'invalidEnv');
$app->boot(); // Throws InvalidArgumentException
// Invalid root path
$app->config()->set('root', 'invalidRoot');
$app->boot(); // Throws InvalidArgumentException
// Invalid slug format
$app->config()->set('slug', 'invalid slug!');
$app->boot(); // Throws InvalidArgumentException
Lifecycle Diagram
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Initialization │ ──▶ │ Configuration │ ──▶ │ Provider │
│ │ │ │ │ Registration │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Termination │ ◀── │ Execution │ ◀── │ Provider │
│ │ │ │ │ Bootstrapping │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Best Practices
-
Register in Register, Initialize in Boot: Use the
register()
method for binding services and theboot()
method for initializing them. -
Keep Providers Focused: Each provider should have a specific responsibility.
-
Validate Configuration Early: Use configuration validation to catch issues before they cause runtime errors.
-
Avoid Circular Dependencies: Be careful not to create circular dependencies between providers.
-
Leverage Configuration Files: Keep configuration in dedicated files (like
config/app.php
,config/database.php
) rather than setting values directly in code.
Related Topics
- Service Providers - Deep dive into creating and using service providers
- Dependency Injection - Understand the service container