APP_ENV is qa17 reasons, each a before and an after. Select the problems you have and see how many match.
A settings class defines one factory per environment. Which APP_ENV
reaches which factory used to live in environment_map β a different
file, and a different repository when the class ships inside a package. Reading the class showed you the method names
and never the environments that reach them.
#[Environment] puts the answer beside the code.
// config/env-settings.php
'environment_map' => [
'qa' => 'staging',
'uat' => 'staging',
],
// app/Settings/AuthSettings.php β says nothing
public static function staging(): static
// app/Settings/AuthSettings.php
#[Environment('qa', 'uat')]
public static function qualityAssurance(): static
// no config entry needed at all
The second form also survives redistribution. Ship that class in a package and it resolves the same way in every application
that installs it β no consumer has to edit their environment_map to make it work.
Four steps, first match wins.
#[Environment] on a method claims this APP_ENV wins
The class said so outright, so a map it cannot see does not get to override it.
environment_map translates APP_ENV to a method name
Unmapped names are tried as-is: APP_ENV=staging looks for staging().
fallback_environment
Configured default, development unless you change it.
development()
The last resort, always present β it is abstract on the base class.
One class, three marked factories, resolved nine times with env-settings:show --env=β¦.
Every row is real output.
#[Environment('local', 'dev')]
public static function development()
#[Environment('production', 'prod')]
#[Environment('demo')]
public static function production()
#[Environment('qa', 'uat')]
public static function qualityAssurance()
| APP_ENV | Resolves to | Why |
|---|---|---|
| local | development() | #[Environment('local', 'dev')] |
| dev | development() | #[Environment('local', 'dev')] |
| production | production() | #[Environment('production', 'prod')] |
| prod | production() | #[Environment('production', 'prod')] |
| demo | production() | #[Environment('demo')] β second attribute on the same method |
| qa | qualityAssurance() | #[Environment('qa', 'uat')] β the method name never matches |
| uat | qualityAssurance() | #[Environment('qa', 'uat')] |
| staging | development() | Nothing claims it, no staging() method β falls to fallback_environment |
| QA | development() | Matching is case-sensitive, so QA is not qa |
qualityAssurance() serves qa.
The method name no longer has to match the environment, so factories can be named for what they mean.
Stack the attribute, or pass several names to one. production,
prod and demo
all reach the same factory above.
APP_ENV=QA does not match
'qa' β it falls through to the fallback, silently. Matching is exact,
because APP_ENV is.
Set 'qa' => 'production' in
environment_map while the class declares
#[Environment('qa')] on another method, and the attribute wins β
verified, not assumed. If two methods claim the same environment, the first declared wins.
PHP does not inherit attributes onto an overridden method, so an override that redeclares a marked factory would lose its
mapping and quietly resolve to development(). The resolver walks the
class hierarchy to prevent exactly that. Only the mapping is inherited β the method still runs on your subclass, so your
override's values are the ones used.