Lift v1.3.0

Configuration Reference

Lift is configured through environment variables (loaded from .env) and/or an in-process array passed to $app->config([...]).

Loading order (highest precedence first):

  1. Real environment variables already set in the shell / server
  2. Values loaded from .env via $app->loadEnv(path)
  3. Values set in PHP config arrays via $app->config([...])

Bootstrap

$app = new App();
$app->loadEnv(__DIR__ . '/../.env');   // loads .env into $_ENV / putenv()
$app->config([                         // optional array overrides
    'app.name' => 'My App',
    'app.env'  => 'production',
]);

Read values anywhere via Env::string() / Env::int() / Env::bool():

use Lift\Config\Env;

$dsn  = Env::string('DB_URL');
$port = Env::int('PORT', 8080);   // default 8080
$flag = Env::bool('FEATURE_X');   // null when absent

Application

Variable Type Default Description
APP_NAME string Lift App Human-readable application name.
APP_ENV string production Runtime environment: local, testing, staging, production.
APP_DEBUG bool false Enable debug mode (shows full stack traces, injects toolbar).
APP_KEY string 32-byte encryption key used by Encrypter. Required in production.
APP_URL string http://localhost Canonical application URL (used for link generation).
APP_NAME="My Blog"
APP_ENV=local
APP_DEBUG=true
APP_KEY=base64:...32-byte-random-key...
APP_URL=https://myblog.example.com
$app->debug(['enabled' => Env::bool('APP_DEBUG', false)]);
$app->loadEnv(__DIR__ . '/../.env');
echo $app->environment(); // → 'local'

Bulk-loading a config directory

$app->configure(string $directory) reads every PHP and YAML file in a folder and registers each one under a key equal to its filename (without the extension). This mirrors the classic Laravel convention:

config/
  app.php       → configuration()->get('app.name')
  database.php  → configuration()->get('database.host')
  cache.php     → configuration()->get('cache.driver')
// bootstrap/app.php
$app = new App();
$app->loadEnv(__DIR__ . '/../.env');
$app->configure(__DIR__ . '/../config');   // load every file in config/

config/database.php example:

<?php
use Lift\Config\Env;

return [
    'driver'   => Env::string('DB_CONNECTION', 'sqlite'),
    'host'     => Env::string('DB_HOST', '127.0.0.1'),
    'port'     => Env::int('DB_PORT', 3306),
    'database' => Env::string('DB_DATABASE', 'storage/db.sqlite'),
    'username' => Env::string('DB_USERNAME', ''),
    'password' => Env::string('DB_PASSWORD', ''),
];

Reading the value anywhere:

$driver = $app->configuration()->get('database.driver', 'sqlite');

configure() performs a recursive merge, so calling it multiple times (e.g. loading a base config then overlaying env-specific overrides) is safe:

$app->configure(__DIR__ . '/../config');
$app->configure(__DIR__ . "/../config/{$app->environment()}");  // overlays local/ or testing/

App shortcut methods

App exposes convenience accessors for the most commonly injected services:

$app->router();           // Lift\Routing\Router      — route cache, URL generation
$app->db();               // Lift\Database\Connection  — requires prior singleton binding
$app->logger();           // Lift\Log\Logger           — requires prior singleton binding
$app->events();           // Lift\Events\EventDispatcher — requires prior singleton binding
$app->configuration();    // Lift\Config\Config        — live config repo (all loaded values)

// Emit a response manually (useful in CLI harnesses, benchmarks)
$response = $app->handle($request);
$app->send($response);

db(), logger(), and events() delegate to $app->container()->make(ClassName::class). They throw a ContainerException if you call them before binding the service.


Database

Variable Type Default Description
DB_CONNECTION string sqlite Driver: mysql, pgsql, sqlite.
DB_HOST string 127.0.0.1 Database server host.
DB_PORT int 3306 Database server port (default changes per driver: PgSQL = 5432).
DB_DATABASE string Database name, or relative path for SQLite (e.g. storage/db.sqlite).
DB_USERNAME string Authentication username.
DB_PASSWORD string Authentication password.
DB_CHARSET string utf8mb4 Connection charset (MySQL/MariaDB only).
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret
$dsn = match (Env::string('DB_CONNECTION', 'sqlite')) {
    'mysql'  => sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s',
                    Env::string('DB_HOST', '127.0.0.1'),
                    Env::string('DB_PORT', '3306'),
                    Env::string('DB_DATABASE'),
                    Env::string('DB_CHARSET', 'utf8mb4')),
    'pgsql'  => sprintf('pgsql:host=%s;port=%s;dbname=%s',
                    Env::string('DB_HOST', '127.0.0.1'),
                    Env::string('DB_PORT', '5432'),
                    Env::string('DB_DATABASE')),
    default  => 'sqlite:' . Env::string('DB_DATABASE', 'storage/database.sqlite'),
};
$db = new Connection($dsn, Env::string('DB_USERNAME'), Env::string('DB_PASSWORD'));
$app->instance(Connection::class, $db);

Session

Variable Type Default Description
SESSION_DRIVER string file Store driver: file, array, database, redis, memcached.
SESSION_PATH string storage/sessions Directory for file-based sessions.
SESSION_LIFETIME int 7200 Session TTL in seconds.
SESSION_COOKIE string lift_session Cookie name sent to the browser.
SESSION_TABLE string sessions Table name for the database store.
SESSION_DRIVER=file
SESSION_PATH=storage/sessions
SESSION_LIFETIME=7200
SESSION_COOKIE=myapp_session
$store = match (Env::string('SESSION_DRIVER', 'file')) {
    'database'  => new DatabaseSessionStore($db, Env::string('SESSION_TABLE', 'sessions')),
    'redis'     => new RedisSessionStore($redis),
    'memcached' => new MemcachedSessionStore($memcached),
    'array'     => new ArraySessionStore(),
    default     => new FileSessionStore(Env::string('SESSION_PATH', 'storage/sessions')),
};
$session = new Session(
    $store,
    lifetime:   Env::int('SESSION_LIFETIME', 7200),
    cookieName: Env::string('SESSION_COOKIE', 'lift_session'),
);
$app->use(new SessionMiddleware($session));

Cache

Variable Type Default Description
CACHE_DRIVER string array Driver: array, redis.
CACHE_PREFIX string lift_ Prefix prepended to every cache key.
CACHE_TTL int 3600 Default TTL in seconds when none is specified.
CACHE_HMAC_SECRET string Required for Redis cache integrity in production.
CACHE_DRIVER=redis
CACHE_PREFIX=myapp_
CACHE_HMAC_SECRET=change-me-to-32-random-bytes
$cache = match (Env::string('CACHE_DRIVER', 'array')) {
    'redis' => new RedisCache(
        $redis,
        Env::string('CACHE_PREFIX', 'lift_'),
        secret: Env::string('CACHE_HMAC_SECRET') ?? throw new \RuntimeException('CACHE_HMAC_SECRET is required'),
    ),
    default => new ArrayCache(),
};
$app->instance(CacheInterface::class, $cache);
$app->instance(\Psr\SimpleCache\CacheInterface::class, new Psr16Adapter($cache));

Queue

Variable Type Default Description
QUEUE_DRIVER string sync Driver: sync, array, redis, amqp.
QUEUE_DEFAULT string default Default queue name.
QUEUE_RETRY_AFTER int 90 Seconds before a job is considered failed and retried.
QUEUE_SECRET string HMAC key required by Redis/Database/AMQP queues.
QUEUE_DRIVER=redis
QUEUE_DEFAULT=default
QUEUE_SECRET=change-me-to-32-random-bytes
$queue = match (Env::string('QUEUE_DRIVER', 'sync')) {
    'redis' => new RedisQueue($redis, secret: Env::string('QUEUE_SECRET') ?? throw new \RuntimeException('QUEUE_SECRET is required')),
    'amqp'  => new AmqpQueue([
                   'host'     => Env::string('RABBITMQ_HOST', 'localhost'),
                   'port'     => Env::int('RABBITMQ_PORT', 5672),
                   'user'     => Env::string('RABBITMQ_USER', 'guest'),
                   'password' => Env::string('RABBITMQ_PASSWORD', 'guest'),
                   'vhost'    => Env::string('RABBITMQ_VHOST', '/'),
                   'secret'   => Env::string('QUEUE_SECRET') ?? throw new \RuntimeException('QUEUE_SECRET is required'),
               ]),
    'array' => new ArrayQueue(),
    default => new SyncQueue(),
};
$app->setQueue($queue);

RabbitMQ environment variables

Used when QUEUE_DRIVER=amqp. Requires composer require php-amqplib/php-amqplib "^3.0".

Variable Type Default Description
RABBITMQ_HOST string localhost RabbitMQ server host.
RABBITMQ_PORT int 5672 AMQP port.
RABBITMQ_USER string guest AMQP username.
RABBITMQ_PASSWORD string guest AMQP password.
RABBITMQ_VHOST string / Virtual host.
QUEUE_DRIVER=amqp
RABBITMQ_HOST=rabbitmq.internal
RABBITMQ_USER=myapp
RABBITMQ_PASSWORD=secret
RABBITMQ_VHOST=/myapp

Redis

Used by Redis-backed cache, queue, and session drivers.

Variable Type Default Description
REDIS_HOST string 127.0.0.1 Redis server host.
REDIS_PORT int 6379 Redis server port.
REDIS_PASSWORD string Redis AUTH password (omit for no auth).
REDIS_DB int 0 Redis database index.
REDIS_PREFIX string lift: Key prefix for all Lift Redis keys.
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=secret
REDIS_DB=0

Logging

Variable Type Default Description
LOG_CHANNEL string stdout Channel: file, stdout, null.
LOG_LEVEL string debug Minimum level: debug, info, notice, warning, error, critical, alert, emergency.
LOG_PATH string storage/logs/app.log File path when LOG_CHANNEL=file.
LOG_FORMAT string line Formatter: line, json.
LOG_CHANNEL=file
LOG_LEVEL=warning
LOG_PATH=storage/logs/app.log
LOG_FORMAT=json
$formatter = Env::string('LOG_FORMAT', 'line') === 'json'
    ? new JsonFormatter()
    : new LineFormatter();

$handler = match (Env::string('LOG_CHANNEL', 'stdout')) {
    'file'   => new FileHandler(Env::string('LOG_PATH', 'storage/logs/app.log'),
                                Env::string('LOG_LEVEL', 'debug'), $formatter),
    'null'   => new NullHandler(),
    default  => new StdoutHandler(Env::string('LOG_LEVEL', 'debug'), $formatter),
};
$logger = new Logger([$handler]);
$app->instance(Logger::class, $logger);

Views

Variable Type Default Description
VIEWS_PATH string views/ Path to the views root directory.
VIEWS_EXTENSION string php File extension for view files (without dot).
ASSET_BASE string /assets URL prefix prepended to relative asset paths via $view->asset().
VIEWS_PATH=resources/views
VIEWS_EXTENSION=php
ASSET_BASE=/static
$app->views(
    path:      Env::string('VIEWS_PATH', __DIR__ . '/../views'),
    extension: Env::string('VIEWS_EXTENSION', 'php'),
    assetBase: Env::string('ASSET_BASE', '/assets'),
);

Debug Toolbar

Enabled only when APP_DEBUG=true. Never enable in production.

Variable Type Default Description
DEBUG_TOOLBAR bool true Show the HTML toolbar on responses.
DEBUG_TOOLBAR_POSITION string bottom-right Position: bottom-right, bottom-left.
DEBUG_TRACK_PHP_ERRORS bool true Capture PHP notices/warnings.
DEBUG_RENDER_EXCEPTIONS bool true Render full exception pages instead of generic 500 errors.
$app->debug([
    'enabled'           => Env::bool('APP_DEBUG', false),
    'toolbar'           => Env::bool('DEBUG_TOOLBAR', true),
    'position'          => Env::string('DEBUG_TOOLBAR_POSITION', 'bottom-right'),
    'track_php_errors'  => Env::bool('DEBUG_TRACK_PHP_ERRORS', true),
    'render_exceptions' => Env::bool('DEBUG_RENDER_EXCEPTIONS', true),
]);

JWT

Variable Type Default Description
JWT_SECRET string Signing secret for HS256/HS384/HS512. Required.
JWT_ALGORITHM string HS256 Algorithm: HS256, HS384, HS512, RS256 (requires key files).
JWT_TTL int 3600 Token time-to-live in seconds.
JWT_ISSUER string Optional iss claim value.
JWT_AUDIENCE string Optional aud claim value.
JWT_SECRET=your-256-bit-secret
JWT_ALGORITHM=HS256
JWT_TTL=3600
$jwt = new Jwt(
    secret:    Env::string('JWT_SECRET') ?? throw new \RuntimeException('JWT_SECRET is required'),
    algorithm: JwtAlgorithm::from(Env::string('JWT_ALGORITHM', 'HS256')),
    ttl:       Env::int('JWT_TTL', 3600),
);
$app->use(new JwtMiddleware($jwt));

CORS

Variable Type Default Description
CORS_ALLOWED_ORIGINS string * Comma-separated allowed origins, or * for all.
CORS_ALLOWED_METHODS string GET,POST,PUT,PATCH,DELETE,OPTIONS Comma-separated HTTP methods.
CORS_ALLOWED_HEADERS string Content-Type,Authorization Comma-separated request headers.
CORS_EXPOSED_HEADERS string Comma-separated response headers exposed to the browser.
CORS_MAX_AGE int 0 Seconds to cache the preflight response.
CORS_ALLOW_CREDENTIALS bool false Allow cookies / auth in cross-origin requests.
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
CORS_ALLOW_CREDENTIALS=true
CORS_MAX_AGE=86400
$app->use(new CorsMiddleware(
    allowedOrigins:    explode(',', Env::string('CORS_ALLOWED_ORIGINS', '*')),
    allowedMethods:    explode(',', Env::string('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS')),
    allowedHeaders:    explode(',', Env::string('CORS_ALLOWED_HEADERS', 'Content-Type,Authorization')),
    allowCredentials:  Env::bool('CORS_ALLOW_CREDENTIALS', false),
    maxAge:            Env::int('CORS_MAX_AGE', 0),
));

Storage / Filesystem

Variable Type Default Description
STORAGE_DRIVER string local Disk driver: local (additional adapters can be registered).
STORAGE_PATH string storage/app Root directory for the default local disk.
STORAGE_URL string Public URL prefix for files on the default disk.
STORAGE_DRIVER=local
STORAGE_PATH=storage/app
STORAGE_URL=https://cdn.example.com/files
$storage = new Storage();
$storage->addDisk('local', new LocalFilesystem(
    root:      Env::string('STORAGE_PATH', __DIR__ . '/../storage/app'),
    publicUrl: Env::string('STORAGE_URL'),
));
$storage->setDefault('local');
Storage::setInstance($storage);
$app->instance(Storage::class, $storage);

Rate Limiting

Variable Type Default Description
RATE_LIMIT_MAX int 60 Maximum requests per window.
RATE_LIMIT_WINDOW int 60 Window size in seconds.
$app->use(new RateLimitMiddleware(
    maxRequests: Env::int('RATE_LIMIT_MAX', 60),
    windowSecs:  Env::int('RATE_LIMIT_WINDOW', 60),
));

Security Headers

SecurityHeadersMiddleware ships with safe defaults. No environment variables are required — configure it directly in code:

$app->use(new SecurityHeadersMiddleware(
    contentSecurityPolicy: "default-src 'self'",
    frameOptions:          'DENY',
    xssProtection:         '1; mode=block',
    noSniff:               true,
    referrerPolicy:        'strict-origin-when-cross-origin',
    hsts:                  'max-age=31536000; includeSubDomains',
));

CSRF

Variable Type Default Description
CSRF_TOKEN_NAME string _token Form field and session key name for the CSRF token.
CSRF_HEADER_NAME string X-CSRF-Token HTTP header name accepted as an alternative to the form field.
CSRF_COOKIE_NAME string csrf_token Cookie name containing the token for JS-driven SPAs.
$app->use(new CsrfMiddleware(
    session:     $session,
    tokenName:   Env::string('CSRF_TOKEN_NAME', '_token'),
    headerName:  Env::string('CSRF_HEADER_NAME', 'X-CSRF-Token'),
));

HTTP Client

Configure defaults for outgoing requests:

$client = HttpClient::new()
    ->timeout(Env::int('HTTP_CLIENT_TIMEOUT', 30))
    ->withHeaders([
        'User-Agent' => Env::string('APP_NAME', 'Lift App') . '/1.0',
    ]);
$app->instance(HttpClient::class, $client);

Full .env example

# Application
APP_NAME="My Lift App"
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:ZmRzZmRzZmRzZmRzZmRzZmRzZmRzZmRzZmRzZmQ=
APP_URL=https://myapp.example.com

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=myapp_user
DB_PASSWORD=secret

# Session
SESSION_DRIVER=file
SESSION_PATH=storage/sessions
SESSION_LIFETIME=7200

# Cache
CACHE_DRIVER=redis
CACHE_PREFIX=myapp_

# Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# Queue
QUEUE_DRIVER=redis

# Logging
LOG_CHANNEL=file
LOG_LEVEL=warning
LOG_PATH=storage/logs/app.log

# JWT
JWT_SECRET=your-very-long-random-secret-key-here
JWT_TTL=3600

# CORS
CORS_ALLOWED_ORIGINS=https://myapp.example.com