devdojo/config-writer
A Laravel package for writing and updating config values in PHP config files at runtime, letting you persist settings changes without editing files by hand.
Install
composer require devdojo/config-writer
There is no documented service provider contract worth relying on. Before writing a line of application code, open the package's composer.json and src/ — the whole thing is small enough to read in one sitting (it is a DevDojo/Wave internal utility extracted into a package, not a maintained ecosystem library). Confirm:
extra.laravel.providers / aliases).src/.vlucas/phpdotenv directly or hand-rolls string replacement.Where to look first
src/ — typically two concerns: a config file writer (reads config/foo.php with include, mutates the array, re-exports with var_export) and an env writer (line-based key/value replacement in .env).tests/ — if present, they are the only reliable spec for edge-case behaviour (quoting, ordering, comments).config/ file shipped by the package for path overrides.First use case
The canonical shape of this package is an installer/settings screen: an admin submits SMTP or API credentials, and you persist them so the next request picks them up without a redeploy.
use DevDojo\ConfigWriter\ConfigWriter; // verify namespace against src/
$writer = new ConfigWriter();
// Persist into config/mail.php
$writer->writeConfig('mail', 'from.address', $request->input('from_address'));
// Persist into .env (illustrative signature)
$writer->writeEnv('MAIL_FROM_ADDRESS', $request->input('from_address'));
Artisan::call('config:clear');
If the package only exposes one of these two capabilities, use it for that one and reach for laravel/framework's own helpers or a hand-rolled class for the other — do not bend the API.
1. Installer / first-run wizard The primary pattern. Treat the writer as the persistence layer of a setup flow: validate input with a FormRequest, write config and env in a transaction-ish sequence, then clear caches. Keep the write behind an artisan command or controller action that returns a clear success/failure response, because the failure modes (permissions) are user-fixable.
2. Wrap it in your own class
Do not scatter new ConfigWriter() through the codebase. Bind an interface in a service provider and implement it with this package. Given the package's adoption profile (1 star, 0 dependents, last release May 2024), you want a one-file swap path when it goes stale or when Wave's assumptions don't match yours.
// app/Contracts/SettingsWriter.php
interface SettingsWriter {
public function put(string $key, mixed $value): void;
}
Register your ConfigWriterSettingsWriter in AppServiceProvider and inject the contract everywhere else.
3. Write-through with an in-memory update A config-file write does not affect the running request if the config is already loaded (or cached). Pair every write with an in-memory set so the current request sees the new value:
$writer->writeConfig('services.stripe', 'key', $value);
config(['services.stripe.key' => $value]); // current process
Artisan::call('config:clear'); // future processes
4. Idempotent bootstrapping Guard writes so re-running an installer is safe:
if (empty(config('mail.from.address'))) {
$writer->writeConfig('mail', 'from.address', $default);
}
5. Artisan commands as the call site
Prefer php artisan app:set-mail-config over a web route that writes files. It sidesteps most permission/deploy issues, is trivially scriptable in CI, and makes the operation auditable. The web form can Artisan::call() it if needed.
6. Testing against a temp path
Copy the relevant config/*.php and .env into a Storage::fake-style temp directory, point the writer at it (if it accepts a path), assert on the resulting file contents, then restore. Snapshot the file after a write and diff — this package's output formatting (indentation, trailing newlines, array syntax) is the thing most likely to break your git diffs.
7. Deployment scaffolding
Useful for generating a .env for a new environment from a template: read a stub, write environment-specific values, hand it off to your provisioning step. This is a better fit than runtime writes if your production filesystem is read-only (Vapor, containers with immutable images).
Config cache silently wins. If config:cache has run, your beautifully written config/foo.php is never read. Any write must be followed by config:clear (and ideally queue:restart for long-running workers). Forgetting this is the number one "the package doesn't work" bug report.
Filesystem permissions. PHP needs write access to config/ and .env. On Forge/Ploi the forge user owns these; under a web-server-context CLI (or with www-data running PHP-FPM) writes fail. Under Docker/Kubernetes with a read-only root filesystem, this package cannot function at all — prefer env vars injected by the platform.
.env parsing is where hand-rolled writers break. Watch for:
# (treated as a comment by naive parsers).= (must be quoted)."\"foo\"".KEY= vs KEY="").Test each of these explicitly against your own .env before shipping. If the package's implementation does a plain preg_replace on the line, assume it does not handle them all.
No atomicity, no locking. Expect a file_put_contents-style write. Two concurrent requests writing different keys can lose one update, and a crash mid-write can truncate the file. For anything beyond single-admin installers, wrap writes in a queue job with WithoutOverlapping, or use a database-backed settings table and materialise config from it at boot.
Env changes don't apply to the current process. Changing .env does not update $_ENV/getenv()/env() in the running request. This is expected PHP behaviour but surprises people who write .env and then call env('NEW_KEY') in the same request.
Version pin it. Last release May 2024, one star, zero dependents — assume no maintainer response and no security patches. Pin an exact version ("devdojo/config-writer": "1.0.0") and put a composer outdated check in CI so a silent break is noticed. Budget for vendoring the two classes into app/Support/ if the package rots; the code is small enough that this is genuinely the lower-risk option for anything long-lived.
Extension points are thin. Realistically your options are: subclass the writer and override the file-writing method, bind your own implementation behind an interface, or copy the source. There is no plugin system, no events, and no config file to tune behaviour. Plan accordingly and keep the surface area of your integration to one class.
Debugging checklist when a write "doesn't take":
cat the file — did it actually change?php artisan config:clear / check bootstrap/cache/config.php exists.ls -l .env config/)..env.included by another process.Don't let writes touch version control. .env should already be gitignored; make sure any generated config/ files that contain secrets are covered too, or write secrets exclusively to .env and read them via env() in your config files.
How can I help you explore Laravel packages today?