17 reasons, each a before and an after. Select the problems you have and see how many match.
Putting settings in version control solves the review problem and creates a new one: the file is now shared. You want a longer timeout while debugging, or a local Ollama instead of the hosted model β without editing a file your teammates pull.
Drop a subclass in app/Settings/Overrides/, gitignore it, and it is yours alone.
The file name must match the class it overrides β Overrides/AiSettings.php overrides AiSettings.
// app/Settings/Overrides/AiSettings.php
namespace App\Settings\Overrides;
use App\Settings\AiSettings as BaseAiSettings;
class AiSettings extends BaseAiSettings
{
public static function production(): static
{
return new static(
provider: 'ollama',
text_model: 'my-local-llama',
embeddings_model: 'nomic-embed-text',
max_tokens: 512,
temperature: 0.7,
);
}
}
The whole point is that nobody else gets it.
# .gitignore
/app/Settings/Overrides
The flag lives in .env, which is already per-machine.
# .env
ENV_SETTINGS_OVERRIDE=true
Without step 3 the file is inert. An override sitting on disk with the flag off changes nothing β verified: the demo still
resolved gpt-4o until ENV_SETTINGS_OVERRIDE=true was set.
Real output, captured with the override above active in this demo under APP_ENV=production.
| Reading it through | text_model | Why |
|---|---|---|
| php artisan env-settings:show | my-local-llama | Resolves through the container, so the override wins. |
| envSettings(AiSettings::class) | my-local-llama | Your application gets the override β that is the point. |
| env-settings:show --all | gpt-4o | Calls the factories directly, so it shows the committed class. |
| env-settings:diff | gpt-4o | Same β it compares what the class declares, not your machine. |
show resolves through the container, so it tells you what
your application is running. show --all and
diff call the factories directly, so they tell you what the
committed class declares. One answers “what am I running?”, the other “what will my teammates get?”
β and you need both when a bug only reproduces on your machine.
The subclass above defines production() and nothing else. Under APP_ENV=development it resolved llama3.2 / 1000 β the parent's values, untouched. Environments you do not redeclare are inherited.
PaymentSettings has no override file, so it resolved live / 5 exactly as committed. There is no blanket switch β one file overrides one class.
The resolver checks is_subclass_of and ignores anything else, so a same-named class that is unrelated can never be substituted in.
PHP does not inherit attributes onto overridden methods, so the resolver walks the hierarchy β an override that redeclares a method marked #[Environment] keeps its mapping.
The defaults are app/Settings/Overrides and the matching namespace.
Both are configurable, and override_path is resolved at runtime rather
than at config-load time β so a relative value stays correct even when the config cache was built somewhere else.
// config/env-settings.php
'override' => env('ENV_SETTINGS_OVERRIDE', false),
// null β app_path('Settings/Overrides')
// 'Custom/Overrides' β app_path('Custom/Overrides')
// '/mnt/shared/overrides' β used as-is
'override_path' => null,
'override_namespace' => 'App\Settings\Overrides',