17 reasons, each a before and an after. Select the problems you have and see how many match.
Your currency is EUR everywhere. Your timeout is
30 everywhere. Writing them into four factories means four
places to change and four chances to miss one — and the day you miss one, two environments quietly disagree.
4
environments
2
constant properties
8
repeated literals
development(): new static(
provider: 'ollama',
currency: 'EUR', temperature: 0.2,
);
staging(): new static(
provider: 'openai',
currency: 'EUR', temperature: 0.2,
);
production(): new static(
provider: 'openai',
currency: 'EUR', temperature: 0.2,
);
Change the currency and you edit three lines. Miss one and it drifts.
__construct(
public string $provider, // varies
public string $currency = 'EUR', // once
public float $temperature = 0.2, // once
) {}
development(): new static(provider: 'ollama');
production(): new static(provider: 'openai');
// the one that differs just passes it:
staging(): new static(provider: 'openai', temperature: 0.9);
One place to change. The exception is still allowed, and it is obvious.
This is a plain PHP constructor default — nothing the package invents. A shared()
array merged in by the package would mean building instances reflectively instead of with
new static(...), which costs the constructor's own type checking,
named-argument autocomplete, and the ability to read a settings class as ordinary PHP.
So it works with show,
--all,
diff,
check, masking, local overrides and
toArray() — not because each was taught about it, but because
there is nothing new to teach them.
--shared takes
name=value pairs and writes them as defaults instead of repeating them
in every factory. Output below is real.
Run
php artisan env-settings:make ProbeSharedSettings \
--properties="provider:string,currency:string,timeout:int" \
--shared="currency=EUR,timeout=30"
Get
public function __construct(
public string $provider,
// Same in every environment — an environment
// that differs passes its own.
public string $currency = 'EUR',
public int $timeout = 30,
) {}
public static function development(): static
{
return new static(
provider: '', // TODO: set development value
);
}
Note which properties got a // TODO: only
provider. The shared values are already set, so
env-settings:check
has nothing to complain about — it only chases the values that were genuinely left blank.
Currency, a default page size, a retry backoff your team standardised on. Give it a default and let the rare exception pass its own value — which then reads as a deliberate difference rather than one of four near-identical literals.
Two environments happening to share a hostname this week is not a constant. If the value is meant to differ,
keep it in the factories where --all and
diff will keep showing it to you.