PHP

Symfony

Load Envless variables in Symfony with envless/env: an env var loader that resolves %env()% from Envless, or a load in the front controller, with notes for .env, Messenger workers, Twig and the Symfony Runtime.

Symfony resolves %env(NAME)% parameters at runtime, from $_ENV, $_SERVER and getenv(). envless/env plugs into that in one of two ways. Pick one.

bash
composer require envless/env

Option 1: the env var loader

Symfony asks every service tagged container.env_var_loader for a variable it cannot find anywhere else. Envless\Env\Symfony\EnvVarLoader answers with your Envless variables, loading them the first time Symfony asks:

yaml
services:
    Envless\Env\Symfony\EnvVarLoader:
        tags: [container.env_var_loader]
php
use Symfony\Component\DependencyInjection\Attribute\Autowire;

final class Checkout
{
    public function __construct(
        #[Autowire(env: 'STRIPE_SECRET')] private readonly string $stripeSecret,
        #[Autowire(env: 'int:CHECKOUT_TIMEOUT')] private readonly int $timeout,
    ) {
    }
}

Nothing changes in public/index.php or bin/console. When Env::isConfigured() is false, as in a composer install without credentials that runs cache:clear, the loader returns nothing and Symfony falls back to its defaults and .env, so the build does not need secrets.

Option 2: load in the front controller

To have every variable in $_ENV, $_SERVER and getenv() before the kernel boots, load inside the closure your front controller returns, in public/index.php and the same way in bin/console:

php
<?php

use App\Kernel;
use Envless\Env\Env;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return static function (array $context) {
    if (Env::isConfigured()) {
        Env::load();
    }

    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

Do not require 'vendor/autoload.php' above autoload_runtime.php to load earlier. The Symfony Runtime includes the front controller a second time and stops at once when it finds the autoloader already included, so the page renders nothing at all.

.env and Envless together

Both options let .env win for a name both define. The Symfony Runtime loads .env into $_ENV and $_SERVER before the closure runs, and envless/env never overwrites a variable that is already set, while the env var loader is only asked about names Symfony found nowhere else. Move a variable to Envless by deleting it from .env and .env.local. The credentials, ENVLESS_TOKEN, ENVLESS_KEY, ENVLESS_PROJECT and ENVLESS_ENV, come from the real environment, or the slugs from a .envless file.

Reading values

php
use Envless\Env\Env;

$port = Env::number('PORT');
$checkout = Env::boolean('CHECKOUT_ENABLED');

%env()% and #[Autowire(env: ...)] keep working as they do today, with Symfony's processors such as int:, bool: and json:. In your own code, Env::string() and its siblings are typed and throw on a missing name. Generate a class with a method per variable with vendor/bin/envless-env types, --output src/Env/EnvlessEnv.php --namespace 'App\Env'.

Messenger workers

bin/console messenger:consume loads once per worker process, through the loader the first time a service needs a variable, or through the front controller closure at boot. messenger:stop-workers makes the next workers load again.

Client variables in Twig

php
use Envless\Env\Env;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

final class CheckoutController extends AbstractController
{
    public function __invoke(): Response
    {
        return $this->render('checkout.html.twig', ['env' => Env::client()->snapshot()]);
    }
}
twig
<script>
    window.env = {{ env|json_encode|raw }};
</script>

Only the variables marked client in Envless are in the snapshot, so a server secret cannot reach the template by accident.