
# Environment

`Environment` is the application's configuration as code sees it: every property from every source, and the active profiles.

## Why

Most beans never need it. `@Value` and `@ConfigurationProperties` read properties for them, and `@Profile` switches beans on profiles. Take the `Environment` when:

- the key is only known at run time (`shop.banner.$language`);
- a decision in code depends on the active profiles;
- text from elsewhere contains `${...}` placeholders to fill.

The environment is a bean. A constructor parameter of type `Environment` receives it; `EnvironmentAware` works too. Placeholders in values are resolved and relaxed names are found.

`Environment` is read-only. `ConfigurableEnvironment` is the side that changes it: property sources, profiles, conversion. It is for startup code, such as an `EnvironmentPostProcessor`, and for tests.

## Example

```dart
import 'package:spring_core_dart/spring_core_dart.dart';

@Service()
class FeatureFlags {
  FeatureFlags(this.env);
  final Environment env;

  // Converted to the requested type by the ConversionService.
  bool get betaOn => env.getPropertyAs<bool>('shop.beta', defaultValue: false)!;
  int get maxItems => env.getPropertyAs<int>('shop.max-basket-size') ?? 10;
  Duration? get timeout => env.getPropertyAs<Duration>('shop.checkout-timeout');

  // The key depends on the language, so @Value cannot name it.
  String banner(String language) =>
      env.getProperty('shop.banner.$language') ??
      env.resolvePlaceholders(r'Welcome to ${shop.title}');

  // Throws MissingPropertyException when no source has the key.
  String get title => env.getRequiredProperty('shop.title');

  // Tells "set to empty" from "not set".
  bool get hasMotto => env.containsProperty('shop.motto');

  bool get showDebugInfo => env.matchesProfiles(['dev & !test']);
}
```

Reading the configuration without a context, in a command-line tool or a test:

```dart
Future<void> main(List<String> args) async {
  final environment = StandardEnvironment()
    ..propertySources.addFirst(SimpleCommandLinePropertySource(args));
  // Reads application.* and the profile files, as SpringApplication does.
  await ConfigDataEnvironment(environment).processAndApply();
  final port = environment.getPropertyAs<int>('server.port');
}
```

A test can replace the OS environment variables:

```dart
final environment = StandardEnvironment(
  systemEnvironment: {'SPRING_PROFILES_ACTIVE': 'test'},
);
environment.activeProfiles; // ['test']
```

## Members

`Environment` (and `PropertyResolver`, which it implements):

| Member | Meaning |
|---|---|
| `getProperty(key, {defaultValue})` | The value as text, placeholders resolved, or `defaultValue` (`null` unless given). |
| `getPropertyAs<T>(key, {defaultValue})` | The value converted to `T`: `int`, `bool`, `Duration`, `List<String>`, enums... `defaultValue` also applies when the value is blank. Throws a `ConversionException` when it cannot be converted. |
| `getRequiredProperty(key)` | The value as text. Throws `MissingPropertyException` when no source has it. |
| `getRequiredPropertyAs<T>(key)` | The value converted to `T`. Throws when it is missing. |
| `containsProperty(key)` | Whether some source has the key, even with an empty value. |
| `resolvePlaceholders(text)` | Replaces `${...}` in `text`. An unresolvable placeholder without a default stays as it is. |
| `resolveRequiredPlaceholders(text)` | The same, but throws `PlaceholderResolutionException` for an unresolvable placeholder. |
| `activeProfiles` | The active profiles, such as `['prod', 'eu']`. Empty when none is active. |
| `defaultProfiles` | The profiles used while none is active: `['default']` unless `spring.profiles.default` says otherwise. |
| `matchesProfiles(expressions)` | Whether any profile expression holds: `['dev & (eu \| us)']`. |
| `acceptsProfiles(profiles)` | The same with a `Profiles` built once: `Profiles.of(['prod', 'staging'])`. |

`ConfigurableEnvironment` adds:

| Member | Meaning |
|---|---|
| `propertySources` | The ordered `MutablePropertySources`. For each key, the first source that has it wins. See [Property sources](property-source.md). |
| `setActiveProfiles(profiles)` | Replaces the active profiles. `spring.profiles.active` is then no longer read. |
| `addActiveProfile(profile)` | Adds a profile and keeps the others, including those from `spring.profiles.active`. |
| `setDefaultProfiles(profiles)` | Replaces `['default']`. |
| `systemEnvironment` | The environment variables of the process, read-only. Empty on the web. |
| `merge(parent)` | Takes over a parent's sources (below its own) and profiles. Used for child contexts. |
| `conversionService` | The service used by `getPropertyAs` and by binding. |
| `setRequiredProperties(keys)`, `validateRequiredProperties()` | Fail early, listing every missing key. |
| `placeholderPrefix`, `placeholderSuffix`, `valueSeparator`, `escapeCharacter` | The placeholder syntax: `${`, `}`, `:` and `\` by default. |
| `ignoreUnresolvableNestedPlaceholders` | Whether an unresolvable placeholder inside a value stays as text instead of throwing. |

`StandardEnvironment` is the implementation used everywhere. It starts with one property source, the environment variables. `SpringApplication` adds the command line, `--dart-define` values, the config files and the defaults.

## Changing it at startup

Beans should not change the environment: other beans may already have read it. To add a source or a profile, use an `EnvironmentPostProcessor`. It runs while the environment is prepared, before any bean exists.

```dart
class RegionProfile implements EnvironmentPostProcessor {
  @override
  void postProcessEnvironment(
    ConfigurableEnvironment environment,
    SpringApplication application,
  ) {
    // Machines in the EU data centre run with the `eu` profile.
    if (environment.getProperty('REGION') == 'eu') {
      environment.addActiveProfile('eu');
    }
  }
}

final app = SpringApplication(
  [appBeans],
  environmentPostProcessors: [RegionProfile()],
);
```

A post-processor whose `order` is smaller than `EnvironmentPostProcessor.configDataOrder` (implement `Ordered`) runs before the config files are read, so its profiles and sources decide which files are loaded. The others run after.

## In Spring (Java)

```java
@Service
class FeatureFlags {
  private final Environment env;
  FeatureFlags(Environment env) { this.env = env; }

  boolean betaOn() { return env.getProperty("shop.beta", Boolean.class, false); }
  boolean showDebugInfo() { return env.matchesProfiles("dev & !test"); }
}
```

Differences that matter:

- Typed access is `getPropertyAs<T>(key)` instead of `getProperty(key, Class)`. Dart has no overloading.
- `matchesProfiles` takes a list of expressions, not varargs.
- There are no JVM system properties.
- Post-processors are passed to `SpringApplication(environmentPostProcessors: [...])`, not listed in `META-INF/spring.factories`.

## See also

- [Property sources](property-source.md)
- [Profiles](profiles.md)
- [Placeholders](placeholders.md)
- [@Value](value.md)
- [Aware interfaces](../beans/aware.md)
- [ConversionService](../conversion/conversion-service.md)
