Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions docs/1-essentials/06-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,30 @@ return new PostgresConfig(

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

## Accessing configuration objects
### Accessing configuration objects

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

```php
use Tempest\Core\AppConfig;
use Tempest\Core\Environment;

final readonly class HomeController
final readonly class AboutController
{
public function __construct(
private AppConfig $config,
private Environment $environment,
) {}

#[Get('/')]
public function __invoke(): View
{
return view('home.view.php', environment: $this->config->environment);
return view('about.view.php', environment: $this->environment);
}
}
```

## Updating configuration objects
### Updating configuration objects

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

```php
use Tempest\Support\Random;
Expand Down Expand Up @@ -81,7 +81,7 @@ final class SlackConfig
public string $token,
public string $baseUrl,
public string $applicationId,
public string $userAgent,
public ?string $userAgent = null,
) {}
}
```
Expand Down Expand Up @@ -120,7 +120,7 @@ final class SlackConnector extends HttpConnector

## Per-environment configuration

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

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.

Expand All @@ -135,6 +135,13 @@ return new S3StorageConfig(
);
```

The following suffixes are supported:

- `.prd.config.php`, `.prod.config.php`, and `.production.config.php` for the production environment.
- `.stg.config.php` and `.staging.config.php` for the staging environment.
- `.dev.config.php` and `.local.config.php` for the development environment.
- `.test.config.php` and `.testing.config.php` for the testing environment.

## Disabling the configuration cache

During development, Tempest will discover configuration files every time the framework is booted. In a production environment, configuration files are automatically cached.
Expand Down
12 changes: 12 additions & 0 deletions docs/1-essentials/07-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ $this->console

And many, many more.

## Spoofing the environment

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.

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:

```php
use Tempest\Core\Environment;

$this->container->singleton(Environment::class, Environment::PRODUCTION);
```

## Changing the location of tests

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.
Expand Down
8 changes: 4 additions & 4 deletions packages/cache/src/Commands/CacheStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@
use Tempest\Console\HasConsole;
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\AppConfig;
use Tempest\Core\ConfigCache;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\Environment;
use Tempest\Icon\IconCache;
use Tempest\Support\Str;
use Tempest\View\ViewCache;
use UnitEnum;

use function Tempest\Support\arr;

if (class_exists(\Tempest\Console\ConsoleCommand::class)) {
if (class_exists(ConsoleCommand::class)) {
final readonly class CacheStatusCommand
{
use HasConsole;

public function __construct(
private Console $console,
private Container $container,
private AppConfig $appConfig,
private Environment $environment,
private DiscoveryCache $discoveryCache,
) {}

Expand Down Expand Up @@ -73,7 +73,7 @@ public function __invoke(bool $internal = true): void
},
);

if ($this->appConfig->environment->isProduction() && ! $this->discoveryCache->enabled) {
if ($this->environment->requiresCaution() && ! $this->discoveryCache->enabled) {
$this->console->writeln();
$this->console->error('Discovery cache is disabled in production. This is not recommended.');
}
Expand Down
8 changes: 3 additions & 5 deletions packages/console/src/Middleware/CautionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@
use Tempest\Console\ConsoleMiddlewareCallable;
use Tempest\Console\ExitCode;
use Tempest\Console\Initializers\Invocation;
use Tempest\Core\AppConfig;
use Tempest\Core\Environment;
use Tempest\Discovery\SkipDiscovery;

#[SkipDiscovery]
final readonly class CautionMiddleware implements ConsoleMiddleware
{
public function __construct(
private Console $console,
private AppConfig $appConfig,
private Environment $environment,
) {}

public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next): ExitCode|int
{
$environment = $this->appConfig->environment;

if ($environment->isProduction() || $environment->isStaging()) {
if ($this->environment->requiresCaution()) {
if ($this->console->isForced) {
return $next($invocation);
}
Expand Down
5 changes: 0 additions & 5 deletions packages/core/src/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@

final class AppConfig
{
public Environment $environment;

public string $baseUri;

public function __construct(
public ?string $name = null,

?Environment $environment = null,

?string $baseUri = null,

/** @var class-string<\Tempest\Core\ExceptionProcessor>[] */
Expand All @@ -27,7 +23,6 @@ public function __construct(
*/
public array $insightsProviders = [],
) {
$this->environment = $environment ?? Environment::fromEnv();
$this->baseUri = $baseUri ?? env('BASE_URI') ?? '';
}
}
6 changes: 3 additions & 3 deletions packages/core/src/Commands/DiscoveryGenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
use Tempest\Console\HasConsole;
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\AppConfig;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\DiscoveryCacheStrategy;
use Tempest\Core\DiscoveryConfig;
use Tempest\Core\Environment;
use Tempest\Core\FrameworkKernel;
use Tempest\Core\Kernel;
use Tempest\Core\Kernel\LoadDiscoveryClasses;
Expand All @@ -27,7 +27,7 @@
public function __construct(
private Kernel $kernel,
private DiscoveryCache $discoveryCache,
private AppConfig $appConfig,
private Environment $environment,
) {}

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

if ($strategy === DiscoveryCacheStrategy::NONE) {
$this->info('Discovery cache disabled, nothing to generate.');
Expand Down
8 changes: 3 additions & 5 deletions packages/core/src/ConfigCacheInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,16 @@ final class ConfigCacheInitializer implements Initializer
public function initialize(Container $container): ConfigCache
{
return new ConfigCache(
enabled: $this->shouldCacheBeEnabled(
$container->get(AppConfig::class)->environment->isProduction(),
),
enabled: $this->shouldCacheBeEnabled(),
);
}

private function shouldCacheBeEnabled(bool $isProduction): bool
private function shouldCacheBeEnabled(): bool
{
if (env('INTERNAL_CACHES') === false) {
return false;
}

return (bool) env('CONFIG_CACHE', default: $isProduction);
return (bool) env('CONFIG_CACHE', default: Environment::guessFromEnvironment()->requiresCaution());
}
}
8 changes: 3 additions & 5 deletions packages/core/src/DiscoveryCacheInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@ final class DiscoveryCacheInitializer implements Initializer
public function initialize(Container $container): DiscoveryCache
{
return new DiscoveryCache(
strategy: $this->resolveDiscoveryCacheStrategy(
$container->get(AppConfig::class)->environment->isProduction(),
),
strategy: $this->resolveDiscoveryCacheStrategy(),
);
}

private function resolveDiscoveryCacheStrategy(bool $isProduction): DiscoveryCacheStrategy
private function resolveDiscoveryCacheStrategy(): DiscoveryCacheStrategy
{
if ($this->isDiscoveryGenerateCommand() || $this->isDiscoveryClearCommand()) {
return DiscoveryCacheStrategy::NONE;
}

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

if ($current === DiscoveryCacheStrategy::NONE) {
return $current;
Expand Down
33 changes: 23 additions & 10 deletions packages/core/src/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@

use function Tempest\env;

/**
* Represents the environment in which the application is running.
*/
enum Environment: string
{
case LOCAL = 'local';
case STAGING = 'staging';
case PRODUCTION = 'production';
case CI = 'ci';
case CONTINUOUS_INTEGRATION = 'ci';
case TESTING = 'testing';
case OTHER = 'other';

/**
* Determines if this environment requires caution for destructive operations.
*/
public function requiresCaution(): bool
{
return in_array($this, [self::PRODUCTION, self::STAGING], strict: true);
}

public function isProduction(): bool
{
Expand All @@ -30,24 +40,27 @@ public function isLocal(): bool
return $this === self::LOCAL;
}

public function isCI(): bool
public function isContinuousIntegration(): bool
{
return $this === self::CI;
return $this === self::CONTINUOUS_INTEGRATION;
}

public function isTesting(): bool
{
return $this === self::TESTING;
}

public function isOther(): bool
/**
* Guesses the environment from the `ENVIRONMENT` environment variable.
*/
public static function guessFromEnvironment(): self
{
return $this === self::OTHER;
}
$value = env('ENVIRONMENT', default: 'local');

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

return self::tryFrom($value) ?? throw new EnvironmentValueWasInvalid($value);
}
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/EnvironmentInitalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Tempest\Core;

use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Container\Singleton;

final class EnvironmentInitalizer implements Initializer
{
#[Singleton]
public function initialize(Container $container): Environment
{
return Environment::guessFromEnvironment();
}
}
3 changes: 2 additions & 1 deletion packages/core/src/EnvironmentInsightsProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ final class EnvironmentInsightsProvider implements InsightsProvider

public function __construct(
private readonly AppConfig $appConfig,
private readonly Environment $environment,
) {}

public function getInsights(): array
Expand All @@ -19,7 +20,7 @@ public function getInsights(): array
'PHP version' => PHP_VERSION,
'Composer version' => $this->getComposerVersion(),
'Operating system' => $this->getOperatingSystem(),
'Environment' => $this->appConfig->environment->value,
'Environment' => $this->environment->value,
'Application URL' => $this->appConfig->baseUri ?: new Insight('Not set', Insight::ERROR),
];
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/EnvironmentValueWasInvalid.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ final class EnvironmentValueWasInvalid extends Exception
{
public function __construct(string $value)
{
$possibleValues = arr(Environment::cases())->map(fn (Environment $environment) => $environment->value)->implode(', ');
$possibleValues = arr(Environment::cases())
->map(fn (Environment $environment) => $environment->value)
->join();

parent::__construct("Invalid environment value `{$value}`, possible values are {$possibleValues}.");
parent::__construct("Invalid environment [{$value}]. Possible values are {$possibleValues}.");
}
}
4 changes: 2 additions & 2 deletions packages/core/src/ExceptionHandlerInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ final class ExceptionHandlerInitializer implements Initializer
#[Singleton]
public function initialize(Container $container): ExceptionHandler
{
$config = $container->get(AppConfig::class);
$environment = $container->get(Environment::class);

return match (true) {
PHP_SAPI === 'cli' => $container->get(ConsoleExceptionHandler::class),
$config->environment->isLocal() => $container->get(DevelopmentExceptionHandler::class),
$environment->isLocal() => $container->get(DevelopmentExceptionHandler::class),
default => $container->get(HttpExceptionHandler::class),
};
}
Expand Down
Loading