Skip to content

Commit ad32ffe

Browse files
authored
feat(core)!: make Environment its own source of truth (#1838)
1 parent 25d69dd commit ad32ffe

33 files changed

+221
-131
lines changed

docs/1-essentials/06-configuration.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,30 @@ return new PostgresConfig(
2828

2929
The configuration object above instructs Tempest to use PostgreSQL as its database, replacing the framework's default database, SQLite.
3030

31-
## Accessing configuration objects
31+
### Accessing configuration objects
3232

3333
To access a configuration object, you may inject it from the container like any other dependency.
3434

3535
```php
36-
use Tempest\Core\AppConfig;
36+
use Tempest\Core\Environment;
3737

38-
final readonly class HomeController
38+
final readonly class AboutController
3939
{
4040
public function __construct(
41-
private AppConfig $config,
41+
private Environment $environment,
4242
) {}
4343

4444
#[Get('/')]
4545
public function __invoke(): View
4646
{
47-
return view('home.view.php', environment: $this->config->environment);
47+
return view('about.view.php', environment: $this->environment);
4848
}
4949
}
5050
```
5151

52-
## Updating configuration objects
52+
### Updating configuration objects
5353

54-
To update a property in a configuration object, you may simply assign a new value. Due to the object being a singleton, the modification will be persisted throught the rest of the application's lifecycle.
54+
To update a property in a configuration object, you may simply assign a new value. Due to the object being a singleton, the modification will be persisted through the rest of the application's lifecycle.
5555

5656
```php
5757
use Tempest\Support\Random;
@@ -81,7 +81,7 @@ final class SlackConfig
8181
public string $token,
8282
public string $baseUrl,
8383
public string $applicationId,
84-
public string $userAgent,
84+
public ?string $userAgent = null,
8585
) {}
8686
}
8787
```
@@ -120,7 +120,7 @@ final class SlackConnector extends HttpConnector
120120

121121
## Per-environment configuration
122122

123-
Whenever possible, you should have a single configuration file per feature. You may use the `Tempest\env()` function inside that file to reference credentials and environment-specific values.
123+
Whenever possible, you should have a single configuration file per feature. You may use the {b`Tempest\env()`} function inside that file to reference credentials and environment-specific values.
124124

125125
However, it's sometimes needed to have completely different configurations in development and in production. For instance, you may use S3 for your [storage](../2-features/05-file-storage.md) in production, but use the local filesystem during development.
126126

@@ -135,6 +135,13 @@ return new S3StorageConfig(
135135
);
136136
```
137137

138+
The following suffixes are supported:
139+
140+
- `.prd.config.php`, `.prod.config.php`, and `.production.config.php` for the production environment.
141+
- `.stg.config.php` and `.staging.config.php` for the staging environment.
142+
- `.dev.config.php` and `.local.config.php` for the development environment.
143+
- `.test.config.php` and `.testing.config.php` for the testing environment.
144+
138145
## Disabling the configuration cache
139146

140147
During development, Tempest will discover configuration files every time the framework is booted. In a production environment, configuration files are automatically cached.

docs/1-essentials/07-testing.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ $this->console
125125

126126
And many, many more.
127127

128+
## Spoofing the environment
129+
130+
By default, Tempest provides a `phpunit.xml` that sets the `ENVIRONMENT` variable to `testing`. This is needed so that Tempest can adapt its boot process and load the proper configuration files for the testing environment.
131+
132+
During tests, you may want to test different paths of your application depending on the environment. For instance, you may want to test that certain features are only available in production. To do this, you may override the {b`Tempest\Core\Environment`} singleton:
133+
134+
```php
135+
use Tempest\Core\Environment;
136+
137+
$this->container->singleton(Environment::class, Environment::PRODUCTION);
138+
```
139+
128140
## Changing the location of tests
129141

130142
The `phpunit.xml` file contains a `{html}<testsuite>` element that configures the directory in which PHPUnit looks for test files. This may be changed to follow any rule of your convenience.

packages/cache/src/Commands/CacheStatusCommand.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@
1212
use Tempest\Console\HasConsole;
1313
use Tempest\Container\Container;
1414
use Tempest\Container\GenericContainer;
15-
use Tempest\Core\AppConfig;
1615
use Tempest\Core\ConfigCache;
1716
use Tempest\Core\DiscoveryCache;
17+
use Tempest\Core\Environment;
1818
use Tempest\Icon\IconCache;
1919
use Tempest\Support\Str;
2020
use Tempest\View\ViewCache;
2121
use UnitEnum;
2222

2323
use function Tempest\Support\arr;
2424

25-
if (class_exists(\Tempest\Console\ConsoleCommand::class)) {
25+
if (class_exists(ConsoleCommand::class)) {
2626
final readonly class CacheStatusCommand
2727
{
2828
use HasConsole;
2929

3030
public function __construct(
3131
private Console $console,
3232
private Container $container,
33-
private AppConfig $appConfig,
33+
private Environment $environment,
3434
private DiscoveryCache $discoveryCache,
3535
) {}
3636

@@ -73,7 +73,7 @@ public function __invoke(bool $internal = true): void
7373
},
7474
);
7575

76-
if ($this->appConfig->environment->isProduction() && ! $this->discoveryCache->enabled) {
76+
if ($this->environment->requiresCaution() && ! $this->discoveryCache->enabled) {
7777
$this->console->writeln();
7878
$this->console->error('Discovery cache is disabled in production. This is not recommended.');
7979
}

packages/console/src/Middleware/CautionMiddleware.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@
99
use Tempest\Console\ConsoleMiddlewareCallable;
1010
use Tempest\Console\ExitCode;
1111
use Tempest\Console\Initializers\Invocation;
12-
use Tempest\Core\AppConfig;
12+
use Tempest\Core\Environment;
1313
use Tempest\Discovery\SkipDiscovery;
1414

1515
#[SkipDiscovery]
1616
final readonly class CautionMiddleware implements ConsoleMiddleware
1717
{
1818
public function __construct(
1919
private Console $console,
20-
private AppConfig $appConfig,
20+
private Environment $environment,
2121
) {}
2222

2323
public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next): ExitCode|int
2424
{
25-
$environment = $this->appConfig->environment;
26-
27-
if ($environment->isProduction() || $environment->isStaging()) {
25+
if ($this->environment->requiresCaution()) {
2826
if ($this->console->isForced) {
2927
return $next($invocation);
3028
}

packages/core/src/AppConfig.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@
88

99
final class AppConfig
1010
{
11-
public Environment $environment;
12-
1311
public string $baseUri;
1412

1513
public function __construct(
1614
public ?string $name = null,
1715

18-
?Environment $environment = null,
19-
2016
?string $baseUri = null,
2117

2218
/** @var class-string<\Tempest\Core\ExceptionProcessor>[] */
@@ -27,7 +23,6 @@ public function __construct(
2723
*/
2824
public array $insightsProviders = [],
2925
) {
30-
$this->environment = $environment ?? Environment::fromEnv();
3126
$this->baseUri = $baseUri ?? env('BASE_URI') ?? '';
3227
}
3328
}

packages/core/src/Commands/DiscoveryGenerateCommand.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
use Tempest\Console\HasConsole;
1010
use Tempest\Container\Container;
1111
use Tempest\Container\GenericContainer;
12-
use Tempest\Core\AppConfig;
1312
use Tempest\Core\DiscoveryCache;
1413
use Tempest\Core\DiscoveryCacheStrategy;
1514
use Tempest\Core\DiscoveryConfig;
15+
use Tempest\Core\Environment;
1616
use Tempest\Core\FrameworkKernel;
1717
use Tempest\Core\Kernel;
1818
use Tempest\Core\Kernel\LoadDiscoveryClasses;
@@ -27,7 +27,7 @@
2727
public function __construct(
2828
private Kernel $kernel,
2929
private DiscoveryCache $discoveryCache,
30-
private AppConfig $appConfig,
30+
private Environment $environment,
3131
) {}
3232

3333
#[ConsoleCommand(
@@ -37,7 +37,7 @@ public function __construct(
3737
)]
3838
public function __invoke(): void
3939
{
40-
$strategy = DiscoveryCacheStrategy::make(env('DISCOVERY_CACHE', default: $this->appConfig->environment->isProduction()));
40+
$strategy = DiscoveryCacheStrategy::make(env('DISCOVERY_CACHE', default: $this->environment->requiresCaution()));
4141

4242
if ($strategy === DiscoveryCacheStrategy::NONE) {
4343
$this->info('Discovery cache disabled, nothing to generate.');

packages/core/src/ConfigCacheInitializer.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,16 @@ final class ConfigCacheInitializer implements Initializer
1414
public function initialize(Container $container): ConfigCache
1515
{
1616
return new ConfigCache(
17-
enabled: $this->shouldCacheBeEnabled(
18-
$container->get(AppConfig::class)->environment->isProduction(),
19-
),
17+
enabled: $this->shouldCacheBeEnabled(),
2018
);
2119
}
2220

23-
private function shouldCacheBeEnabled(bool $isProduction): bool
21+
private function shouldCacheBeEnabled(): bool
2422
{
2523
if (env('INTERNAL_CACHES') === false) {
2624
return false;
2725
}
2826

29-
return (bool) env('CONFIG_CACHE', default: $isProduction);
27+
return (bool) env('CONFIG_CACHE', default: Environment::guessFromEnvironment()->requiresCaution());
3028
}
3129
}

packages/core/src/DiscoveryCacheInitializer.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,17 @@ final class DiscoveryCacheInitializer implements Initializer
1414
public function initialize(Container $container): DiscoveryCache
1515
{
1616
return new DiscoveryCache(
17-
strategy: $this->resolveDiscoveryCacheStrategy(
18-
$container->get(AppConfig::class)->environment->isProduction(),
19-
),
17+
strategy: $this->resolveDiscoveryCacheStrategy(),
2018
);
2119
}
2220

23-
private function resolveDiscoveryCacheStrategy(bool $isProduction): DiscoveryCacheStrategy
21+
private function resolveDiscoveryCacheStrategy(): DiscoveryCacheStrategy
2422
{
2523
if ($this->isDiscoveryGenerateCommand() || $this->isDiscoveryClearCommand()) {
2624
return DiscoveryCacheStrategy::NONE;
2725
}
2826

29-
$current = DiscoveryCacheStrategy::make(env('DISCOVERY_CACHE', default: $isProduction));
27+
$current = DiscoveryCacheStrategy::make(env('DISCOVERY_CACHE', default: Environment::guessFromEnvironment()->requiresCaution()));
3028

3129
if ($current === DiscoveryCacheStrategy::NONE) {
3230
return $current;

packages/core/src/Environment.php

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@
66

77
use function Tempest\env;
88

9+
/**
10+
* Represents the environment in which the application is running.
11+
*/
912
enum Environment: string
1013
{
1114
case LOCAL = 'local';
1215
case STAGING = 'staging';
1316
case PRODUCTION = 'production';
14-
case CI = 'ci';
17+
case CONTINUOUS_INTEGRATION = 'ci';
1518
case TESTING = 'testing';
16-
case OTHER = 'other';
19+
20+
/**
21+
* Determines if this environment requires caution for destructive operations.
22+
*/
23+
public function requiresCaution(): bool
24+
{
25+
return in_array($this, [self::PRODUCTION, self::STAGING], strict: true);
26+
}
1727

1828
public function isProduction(): bool
1929
{
@@ -30,24 +40,27 @@ public function isLocal(): bool
3040
return $this === self::LOCAL;
3141
}
3242

33-
public function isCI(): bool
43+
public function isContinuousIntegration(): bool
3444
{
35-
return $this === self::CI;
45+
return $this === self::CONTINUOUS_INTEGRATION;
3646
}
3747

3848
public function isTesting(): bool
3949
{
4050
return $this === self::TESTING;
4151
}
4252

43-
public function isOther(): bool
53+
/**
54+
* Guesses the environment from the `ENVIRONMENT` environment variable.
55+
*/
56+
public static function guessFromEnvironment(): self
4457
{
45-
return $this === self::OTHER;
46-
}
58+
$value = env('ENVIRONMENT', default: 'local');
4759

48-
public static function fromEnv(): self
49-
{
50-
$value = env('ENVIRONMENT', 'local');
60+
// Can be removed after https://github.com/tempestphp/tempest-framework/pull/1836
61+
if (is_null($value)) {
62+
$value = 'local';
63+
}
5164

5265
return self::tryFrom($value) ?? throw new EnvironmentValueWasInvalid($value);
5366
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Tempest\Core;
4+
5+
use Tempest\Container\Container;
6+
use Tempest\Container\Initializer;
7+
use Tempest\Container\Singleton;
8+
9+
final class EnvironmentInitalizer implements Initializer
10+
{
11+
#[Singleton]
12+
public function initialize(Container $container): Environment
13+
{
14+
return Environment::guessFromEnvironment();
15+
}
16+
}

0 commit comments

Comments
 (0)