In Laravel, where does app()->environment() get its value, and how does an externally set APP_ENV choose which .env file loads?
answer
- config key app.env
- defaults to production when unset
- wildcards via Str::is
- .env.{APP_ENV} replaces .env
- isLocal() and isProduction() shortcuts
basics
~10 sapp()->environment() returns the app.env config value, which config/app.php takes from APP_ENV (default 'production'). An APP_ENV set outside the file makes Laravel load .env.{APP_ENV} instead of .env when that file exists.
solid answer
~40 s`config/app.php` sets `'env' => env('APP_ENV', 'production')`, and after configuration loads Laravel stores that value as the application's environment; `app()->environment()` (or `App::environment()`) returns it as a string. Passed arguments, it returns a boolean: `app()->environment('local', 'staging')` is true for either, and patterns go through `Str::is`, so `'stag*'` matches. `isLocal()` and `isProduction()` are shortcuts. Before any of that, the environment-loading bootstrapper checks for an `APP_ENV` already set in the real environment (Artisan's `--env` option plays the same role on the console) and, if `.env.staging` exists, loads **it instead of** `.env`; the files are not layered. That is how `.env.testing` is picked up when `phpunit.xml` sets `APP_ENV=testing`.
code
php · 20 lines<?php
namespace App\Providers;
use App\Payments\FakeGateway;
use App\Payments\Gateway;
use App\Payments\LiveGateway;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(Gateway::class, fn () =>
$this->app->environment('local', 'testing')
? new FakeGateway
: new LiveGateway(config('services.payment.key'))
);
}
}go deeper
Know that APP_ENV names the environment and that App::environment('local') answers whether you are in it. Remember the default is production.
Explain the chain from APP_ENV through config('app.env') to environment(), the Str::is pattern matching, and how an externally set APP_ENV selects .env.{APP_ENV}.
Use environment checks only for wiring such as fake gateways, keep behaviour behind explicit config flags, and make sure production servers export APP_ENV rather than rely on the default.
Decide how many named environments the team supports and how each gets its variables, so staging mirrors production closely and environment names never become hidden feature switches.
## Where the environment name comes from Laravel's "environment" is a string such as `local`, `staging`, `testing` or `production`. It is not read directly from the `.env` file at call time. The chain is: 1. `config/app.php` contains `'env' => env('APP_ENV', 'production')`. If `APP_ENV` is set nowhere, the app believes it is in **production**, which is the safe default. 2. After the config repository is built, the configuration bootstrapper calls `$app->detectEnvironment()` with a callback that returns `config('app.env')`. 3. On the console, an `--env` argument, if present, wins over the config value. 4. The result is stored on the application and returned by `environment()`. Because the value flows through configuration, it is also captured in the config cache like any other key. ## Asking the environment ```php use Illuminate\Support\Facades\App; App::environment(); // 'staging' App::environment('local'); // false App::environment('local', 'staging'); // true (any match) App::environment(['local', 'staging']); // same, as an array App::environment('prod*'); // pattern via Str::is app()->isProduction(); // true only for 'production' ``` - With **no arguments** it returns the name as a string. - With **arguments** it returns a boolean, true when any argument matches; each argument is matched with `Str::is`, so `*` wildcards work. - `isLocal()` and `isProduction()` compare against exactly `local` and `production`. A gym-membership app might use this to register a fake payment gateway in `local` and `testing` and the real one elsewhere. Branching on the environment is fine for wiring like that; business rules are better driven by explicit config flags, so staging can behave like production. ## Environment-specific `.env` files Before the configuration is loaded, the environment-loading bootstrapper decides which file to parse: 1. On the console, Artisan's `--env=NAME` option selects `.env.NAME` when that file exists. 2. Otherwise, if `APP_ENV` is **already set in the real environment** (by the server, the shell or PHPUnit), and `.env.{APP_ENV}` exists, that file is used. 3. Otherwise the plain `.env` is used. Key consequences: - **Replacement, not layering.** `.env.staging` is loaded instead of `.env`. Any variable missing from it is simply absent. - **`APP_ENV` inside `.env` cannot select a file**, because it is only known after a file has been loaded. The selector must come from outside. - **Testing:** the skeleton's `phpunit.xml` sets `APP_ENV=testing`, so a `.env.testing` file, if present, is used for test runs. - **Nothing loads when config is cached.** With a config cache, no `.env` file is parsed at all, and the environment name comes from the cached `app.env` value. ## Comparison | Situation | Environment name | File parsed | |---|---|---| | `.env` has `APP_ENV=local`, nothing external | `local` | `.env` | | Server exports `APP_ENV=staging`, `.env.staging` exists | `staging` | `.env.staging` | | `phpunit.xml` sets `APP_ENV=testing`, `.env.testing` exists | `testing` | `.env.testing` | | `APP_ENV` unset anywhere | `production` | `.env` | ## Good and bad uses of environment checks Checking the environment is useful for wiring that must differ between a laptop and a server: - binding a fake payment gateway in `local` and `testing`; - turning on extra query logging locally; - refusing to run a destructive seeder when `app()->isProduction()` is true. It is a poor fit for business behaviour. If the gym app sends renewal reminders only when `environment('production')` is true, staging can never be used to test reminders, and a misconfigured `APP_ENV` silently changes what members receive. A config flag such as `config('memberships.send_reminders')`, set per environment through its own variable, keeps behaviour explicit and testable. The environment name then describes *where* the app runs, not *what* it does. Blade templates have their own environment directives; those belong to the templating layer, but they read the same value described here. ## Mistakes to avoid - Calling `env('APP_ENV')` in application code: it returns `null` for a value that lived only in `.env` once config is cached. Use `app()->environment()`. - Assuming `.env.staging` inherits from `.env`. - Forgetting that an unset `APP_ENV` means `production`, which can switch on production-only behaviour on a half-configured server.
- A server has .env and .env.staging, and .env contains APP_ENV=staging. Which file does Laravel load?`.env`. The file is chosen before any file is parsed, from an `APP_ENV` that already exists in the real environment. A value inside `.env` is only known after `.env` is loaded, so it cannot redirect to `.env.staging`. To use `.env.staging`, export `APP_ENV=staging` from the server or process manager.
- Does .env.staging add to the values in .env?No. When `.env.staging` is selected it is loaded instead of `.env`, not on top of it. Every variable the app needs must be present in `.env.staging` (or in the real environment); anything missing falls back to the defaults in the config files.
saying these in an interview costs you the question
- app()->environment() reads APP_ENV from .env on every call
- An unset APP_ENV makes the environment default to local
- .env.staging is layered on top of .env
- APP_ENV=staging inside .env makes Laravel load .env.staging
- environment('local', 'staging') is true only if both match