onlineconf / onlineconf-laravel
Laravel integration for onlineconf-php: service provider, facade, config, test helpers and artisan commands
Package info
github.com/onlineconf/onlineconf-php-laravel
pkg:composer/onlineconf/onlineconf-laravel
Requires
- php: ^8.1
- laravel/framework: ^10.0 || ^11.0 || ^12.0
- onlineconf/onlineconf: ^1.0
- psr/log: ^1.1 || ^2 || ^3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^10.5 || ^11.5.3 || ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-20 07:02:38 UTC
README
Laravel integration for onlineconf-php, the PHP client of OnlineConf. Container bindings, a facade, a config file, test helpers and artisan commands — and nothing else: every getter, default, exception and reload rule is the client's, documented in its README.
- Laravel 10, 11 and 12; PHP ≥ 8.1;
ext-dbawith thecdbhandler (see the client README for installation). - No changes to value semantics, no extra caching layer, no application-specific behaviour.
Installation
composer require onlineconf/onlineconf-laravel
php artisan vendor:publish --tag=onlineconf-config # optional: config/onlineconf.php
The service provider and the Onlineconf facade alias are auto-discovered.
Try it without an updater
The client ships ready-made modules in vendor/onlineconf/onlineconf/examples/onlineconf/: TREE.cdb
(strings, numbers, booleans, durations, JSON arrays, child lists) and legacy.cdb (dot-notation keys).
Next to each .cdb lies a .conf with the same content in the updater's text format — a human-readable
listing only, the library never reads it. Point the package at that directory and look around:
export ONLINECONF_DIR=$PWD/vendor/onlineconf/onlineconf/examples/onlineconf php artisan onlineconf:get /app/hosts/main # www.example.com php artisan onlineconf:get --tree /app/nginx/anti-ddos # pretty JSON of the subtree php artisan onlineconf:get --json /app/services/billing/admin_emails | jq . php artisan about --only=onlineconf # directory, module file, version
The same tree works for the config() override below. With ConfigOverride::register($app) installed
and this map in config/onlineconf.php:
'map' => [ 'app.url' => '/app/hosts/main', // string fallback → "www.example.com" 'session.lifetime' => '/app/nginx/anti-ddos/cookie-ttl', // int fallback → 7200 'app.debug' => '/app/nginx/anti-ddos/debug', // bool fallback → false ("0") ],
config('session.lifetime') returns the integer 7200 and config('app.debug') returns false, while
every unmapped key keeps its value from config/*.php. Unset ONLINECONF_DIR (or set it in .env)
when you are done.
Configuration
config/onlineconf.php:
| key | env | default | meaning |
|---|---|---|---|
dir |
ONLINECONF_DIR |
null |
directory with module files; null lets the client resolve it |
module |
ONLINECONF_MODULE |
null |
default module: a name (TREE) or a file path; null = client default |
check_interval |
ONLINECONF_CHECK_INTERVAL |
5 |
seconds between stat() checks for updates, 0 = every access |
log_channel |
ONLINECONF_LOG_CHANNEL |
null |
log channel for the client's warnings; null = default logger |
config_override |
ONLINECONF_CONFIG_OVERRIDE |
true |
kill switch of the config() override below |
map |
— | [] |
Laravel config key → OnlineConf path for the config() override below |
With dir and module unset the client's own resolution applies: ONLINECONF_DIR, ONLINECONF_CONFIG
and CDB_CONFIG_FILE from the process environment, then /usr/local/etc/onlineconf.yaml, then
/usr/local/etc/onlineconf and TREE. Laravel does not export .env values to the process environment,
so a value that lives only in .env reaches the client only through the two config keys above — put
ONLINECONF_DIR (and ONLINECONF_MODULE if needed) into .env, not CDB_CONFIG_FILE. config:cache is
safe: env() is read only inside the config file.
The manager snapshots dir, module, check_interval and log_channel when it is first resolved (the first
Module injection, facade call, or ConfigOverride install) — change them in config/onlineconf.php or
.env, not at runtime. When the config() override is active, log_channel is resolved before service
providers register, so a channel whose driver comes from Log::extend() in a provider cannot be used there
(built-in drivers and 'driver' => 'custom' work).
Usage
Inject the client's Module anywhere:
use Onlineconf\Module; final class Mailer { public function __construct(private readonly Module $config) {} public function send(): void { $host = $this->config->getString('/my/service/smtp/host', 'localhost'); $timeout = $this->config->getDurationMs('/my/service/smtp/timeout', 5000); } }
Or use the facade:
use Onlineconf\Laravel\Facades\Onlineconf; Onlineconf::getInt('/my/service/db/port', 3306); Onlineconf::requireStrings('/my/service/hosts'); Onlineconf::subtree('/my/service')->getBool('/enabled', false); Onlineconf::module('other')->getString('/key', ''); // another module: <dir>/other.cdb or a file path
The facade proxies to the default module; its short name coincides with the client's static registry
Onlineconf\Onlineconf, which this package does not use — import the facade, not the registry.
Paths are always full paths. There is no application prefix; use subtree() where a prefix helps.
Runtime behaviour
- PHP-FPM: one
Moduleper request,dba_openon the first read and onestat()per request. - Octane, Horizon, queue workers, long-running commands: the same
Modulelives as long as the process and reloads itself when the file changes (check_interval). Code that caches values derived from the configuration should compareOnlineconf::version()or callOnlineconf::checkForUpdates()itself; the package adds no hooks. - Opening the module file happens on the first use (first injection of
Module, first facade call), not at boot. A missing or invalid file throws the client'sOpenExceptionat that point.
Testing your application
use Onlineconf\Laravel\Facades\Onlineconf; $source = Onlineconf::fake([ '/my/service/db/host' => 'db.local', // string → s value '/my/service/db/opts' => ['pool' => 5], // array → j value (JSON) '/my/service/flag' => null, // null → empty s value ]); // call fake() before resolving the service under test — a singleton that already received a Module // keeps the old instance $source->replaceValues(['/my/service/db/host' => 'other']); // visible on the next read Onlineconf::fake(['/key' => 'value'], 'other'); // a named module
The same as a trait method for test cases:
use Onlineconf\Laravel\Testing\InteractsWithOnlineconf; final class MailerTest extends TestCase { use InteractsWithOnlineconf; public function testSend(): void { $this->fakeOnlineconf(['/my/service/smtp/host' => 'smtp.example.com']); // ... } }
Fakes need no module files; child lists are generated, so children() and getTree() work on them. A fake
also feeds the config() override below, so config('services.mailer.host') returns the faked value.
Overriding config() values
To migrate settings from .env to OnlineConf without touching the code that calls config(), map config
keys to OnlineConf paths and install the override in bootstrap/app.php.
Laravel 10:
$app = new Illuminate\Foundation\Application( $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ); // ... \Onlineconf\Laravel\ConfigOverride::register($app); return $app;
Laravel 11 and 12:
$app = Application::configure(basePath: dirname(__DIR__)) // ->withRouting(...) // ->withMiddleware(...) // ->withExceptions(...) ->create(); \Onlineconf\Laravel\ConfigOverride::register($app); return $app;
and in the published config/onlineconf.php:
'map' => [ 'services.mailer.host' => '/my/service/mailer/host', 'services.mailer.port' => '/my/service/mailer/port', 'app.debug' => '/my/service/debug', ],
From then on config('services.mailer.host') is read from OnlineConf. The value from config/*.php (usually
env(...)) stays as the fallback and is returned whenever: there is no such key in OnlineConf, the value does
not parse (a warning is logged, as the client always does), the value is invalid JSON (an error is logged), or
the module file cannot be opened (an error is logged once). The OnlineConf value is read with the type of the
fallback: a bool in the config file means getBool, an int means getInt, an array means getArray (a
JSON value in OnlineConf), a string means getString, null means the raw value. Reading config('services')
as a whole includes the mapped keys under it; an explicit config()->set() at runtime wins over the map.
- Migrate one key at a time by adding it to the map;
ONLINECONF_CONFIG_OVERRIDE=falsein.envturns the whole override off. - The map lives in the application's published
config/onlineconf.php: at the moment the override is installed, package defaults are not merged yet, so an unpublished config means an empty map. config:cacheis supported: the override works on top of the cached array.- Reads are lazy, so long-running workers see a changed value on the next
config()call. Services that read their settings once in a constructor keep them, as with any Laravel configuration. set()at runtime unmaps the written key, everything below it and everything above it: an explicit write wins over the map for good, not just until the next read.- The client logs an unparsable mapped value verbatim at
warninglevel (e.g."abc" is not an integer), so a malformed secret can end up in the log. - Not covered:
env()calls outsideconfig/*.phpandgetenv(); move them into a config file first. Also not covered:config()->all()— it returns the loaded configuration without substitution (this is what keepsconfig:cachesafe), so code or packages readingall()see the fallbacks. - Cost: an unmapped key costs one extra array lookup; a mapped key is one
dba_fetchon first read per process, then the client's cache.
Artisan
php artisan onlineconf:get /my/service/db/host
php artisan onlineconf:get --json /my/service/db/opts | jq .
php artisan onlineconf:get --tree /my/service
php artisan onlineconf:get --module=other /key
php artisan about --only=onlineconf
onlineconf:get prints s values as is and j values as the stored JSON text; --json encodes any
value as JSON, --tree prints getTree() as pretty JSON (on a path with no descendants this is the
client's getTree() behaviour: null, exit 0, not a "not found" error). Exit codes: 0; 1 when the
key does not exist; 2 on file, format or invalid-JSON errors. about shows the directory, the default
module file and the version of the loaded data.
Compatibility
The package uses only framework APIs that are identical in Laravel 10, 11 and 12 (singletonIf, bind,
mergeConfigFrom, publishes, commands, AboutCommand::add, afterBootstrapping, facades, the
Illuminate\Config\Repository base class). CI runs the test suite against
all three majors, with PHPStan deprecation rules and PHPUnit failOnDeprecation on, so a deprecated API
fails the build rather than the upgrade.
Development
docker/run.sh composer install # PHP CLI + ext-dba + pcov docker/run.sh composer check # php-cs-fixer --dry-run, phpstan (level max), phpunit with 100% line coverage PHP_VERSION=8.4 docker/run.sh composer check
composer.json also carries a config.policy.advisories.ignore entry for laravel/framework <12.0:
Composer 2.10+ refuses to install a package version with an unpatched security advisory unless it is
explicitly allow-listed, and Laravel 10 and 11 are supported on purpose here, so the entry silences
advisories against those two majors for this package's own composer update.
About this code
The package was written with Claude (Anthropic) from a specification, under human direction and review. Every line is covered by tests, static analysis and CI; the maintainers are responsible for the code as for any other. Issues and pull requests are welcome.
License
MIT.