Architecture
Service providers
Service providers set up your app and every part of Oriel: they bind services in the container, then configure them once every provider has registered.
Introduction#
Service providers are the central place of your app's setup. Your own app, and each of Oriel's components, start through them. Setting up here means registering things: bindings in the service container, event listeners, view composers, route patterns and bindings.
Oriel uses 22 service providers of its own, for the mailer, the queue, the cache and the rest. Request
lifecycle lists them, and shows where providers fit in how your app starts. Your own
providers are listed in bootstrap/. This page shows how to write them and register them.
Writing service providers#
A service provider extends the ServiceProvider class. Most have a register() method and a boot() method. In
register(), only bind things into the service container: never listen for events or set anything else up there, as
the providers that offer what you would use may not have registered yet.
make:provider writes a new provider in app/:
node oriel make:provider RiakIt adds ServiceProvider to the name when the name doesn't end with it, so this writes
app/, with an empty register() and boot():
import { ServiceProvider } from '@orieljs/framework'export class RiakServiceProvider extends ServiceProvider { override register(): void { // } override boot(): void { // }}make:provider doesn't register the provider: add it to bootstrap/ yourself, as Registering
providers shows. --force overwrites a provider that exists.
The register method#
In register(), bind things into the service container, and nothing else. Every provider method has the
application, which is also the container, in this.app:
import { Config, ServiceProvider } from '@orieljs/framework'import { Connection } from '../Services/Riak/Connection'export class RiakServiceProvider extends ServiceProvider { override register(): void { this.app.singleton(Connection, () => new Connection(Config.string('riak.host'), Config.integer('riak.port'))) }}This provider only has a register() method, which tells the container how to build a Connection. Service
container covers the bindings.
register() is synchronous: Oriel doesn't wait for a promise it returns, and the next provider registers at once. Do
async work in boot(), which Oriel awaits. Your configuration is loaded before any provider registers, so
register() can read it.
Default configuration#
A provider that ships with its own settings, as a package's does, gives them to your app with mergeConfigFrom() in
register(). It takes the defaults and the name of the configuration file they belong to:
import { ServiceProvider } from '@orieljs/framework'export class RiakServiceProvider extends ServiceProvider { override register(): void { this.mergeConfigFrom({ host: '127.0.0.1', port: 8087, options: { timeout: 5 } }, 'riak') }}config('riak.port') is then 8087 unless your app's config/ sets a port, which wins. The merge is shallow,
as Laravel's is: each key your app sets replaces the default's whole value. If config/ sets
options: { retries: 3 }, the options are { retries: 3 }, and timeout is gone.
The boot method#
boot() runs once every provider has registered, so every binding of the framework and of your app is in place.
Set things up there, such as a view composer:
import { ServiceProvider, View } from '@orieljs/framework'export class ComposerServiceProvider extends ServiceProvider { override boot(): void { View.composer('profile', (view) => { // ... }) }}boot() may be async: Oriel awaits it before it boots the next provider. The providers boot in the order they
registered, the framework's first, and your routes load after the last one has booted (see Request
lifecycle).
An error thrown in register() or boot() stops your app from starting. Oriel throws a ProviderRegisterError or a
ProviderBootError that names the provider, with the original error as its cause: "Service provider
[RiakServiceProvider] failed to boot: Riak is down".
Boot method dependency injection#
boot() takes no arguments. Resolve what it needs from the container, with this.app.make():
import { ServiceProvider } from '@orieljs/framework'import { ConfigRepository } from '@orieljs/framework/contracts'export class AppServiceProvider extends ServiceProvider { override boot(): void { const config = this.app.make(ConfigRepository) if (config.get('app.debug')) { // ... } }}Registering providers#
Your providers are listed in bootstrap/. It exports a list of the provider classes, which
bootstrap/ gives to withProviders():
import { AppServiceProvider } from '../app/Providers/AppServiceProvider'export default [AppServiceProvider]To register a provider you wrote, add it to the list:
import { AppServiceProvider } from '../app/Providers/AppServiceProvider'import { ComposerServiceProvider } from '../app/Providers/ComposerServiceProvider'export default [AppServiceProvider, ComposerServiceProvider]Oriel registers the framework's providers first, then yours, in the order of the list. A provider listed twice is registered once.
this.app.register() registers a provider while your app runs, as a package's provider might for another one it
needs: await this.app.register(RiakServiceProvider). If the app has booted already, the new provider boots at once.
Deferred providers#
Every provider registers and boots when your app starts: Oriel has no deferred providers.