skip to content

In Laravel, where does app()->environment() get its value, and how does an externally set APP_ENV choose which .env file loads?

level: middleimportance: should knowfreq 48%

answer

  1. config key app.env
  2. defaults to production when unset
  3. wildcards via Str::is
  4. .env.{APP_ENV} replaces .env
  5. isLocal() and isProduction() shortcuts

basics

~10 s

app()->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
<?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

for a junior

Know that APP_ENV names the environment and that App::environment('local') answers whether you are in it. Remember the default is production.

for a middle

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}.

for a senior

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.

for a principal

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