# Laravel Integration

Use KeyEnv with Laravel applications for secure secrets management.

Source: https://keyenv.dev/docs/sdks/laravel/

Integrate KeyEnv with your Laravel application to securely manage secrets like database credentials, API keys, and encryption keys.

## Features

- Load secrets before Laravel initializes
- Environment-aware configuration (local, staging, production)
- Service Provider with auto-discovery
- Facade for convenient access (`KeyEnv::get('key')`)
- Artisan commands for local development
- Works with Laravel's `.env` file pattern
- Supports Laravel 9.x, 10.x, and 11.x

## Installation

```bash
composer require keyenv/keyenv
```

## Quick Start

The simplest approach loads secrets in your `AppServiceProvider`:

```php
// app/Providers/AppServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use KeyEnv\KeyEnv;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Load secrets before config is used
        if ($token = env('KEYENV_TOKEN')) {
            $client = KeyEnv::create($token);
            $client->loadEnv(
                env('KEYENV_PROJECT'),
                env('APP_ENV', 'local')
            );
        }
    }

    public function boot(): void
    {
        //
    }
}
```

## Service Provider Setup

For a more robust setup, create a dedicated KeyEnv service provider:

### Create the Service Provider

```bash
php artisan make:provider KeyEnvServiceProvider
```

```php
// app/Providers/KeyEnvServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use KeyEnv\KeyEnv;

class KeyEnvServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Register the KeyEnv client as a singleton
        $this->app->singleton(KeyEnv::class, function () {
            $token = env('KEYENV_TOKEN');
            if (!$token) {
                return null;
            }
            return KeyEnv::create($token);
        });

        // Load secrets into environment
        $this->loadSecrets();
    }

    protected function loadSecrets(): void
    {
        $token = env('KEYENV_TOKEN');
        $projectId = env('KEYENV_PROJECT');

        if (!$token || !$projectId) {
            return;
        }

        $client = KeyEnv::create($token);
        $environment = $this->mapLaravelEnv(env('APP_ENV', 'local'));

        $client->loadEnv($projectId, $environment);
    }

    protected function mapLaravelEnv(string $laravelEnv): string
    {
        // Map Laravel environments to KeyEnv environments
        return match ($laravelEnv) {
            'local' => 'development',
            'testing' => 'development',
            'staging' => 'staging',
            'production', 'prod' => 'production',
            default => $laravelEnv,
        };
    }

    public function boot(): void
    {
        //
    }
}
```

### Register the Provider

Add to `bootstrap/providers.php` (Laravel 11):

```php
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\KeyEnvServiceProvider::class, // Add this line
];
```

Or in `config/app.php` (Laravel 9/10):

```php
'providers' => ServiceProvider::defaultProviders()->merge([
    App\Providers\KeyEnvServiceProvider::class,
])->toArray(),
```

## Config File Setup

Create a config file for KeyEnv settings:

```php
// config/keyenv.php
return [
    /*
    |--------------------------------------------------------------------------
    | KeyEnv Service Token
    |--------------------------------------------------------------------------
    |
    | Your KeyEnv service token for API authentication.
    | Generate one at: https://app.keyenv.dev/settings/tokens
    |
    */
    'token' => env('KEYENV_TOKEN'),

    /*
    |--------------------------------------------------------------------------
    | KeyEnv Project ID
    |--------------------------------------------------------------------------
    |
    | The ID of your KeyEnv project.
    |
    */
    'project_id' => env('KEYENV_PROJECT'),

    /*
    |--------------------------------------------------------------------------
    | Environment Mapping
    |--------------------------------------------------------------------------
    |
    | Map Laravel environment names to KeyEnv environment names.
    |
    */
    'environments' => [
        'local' => 'development',
        'testing' => 'development',
        'staging' => 'staging',
        'production' => 'production',
    ],

    /*
    |--------------------------------------------------------------------------
    | Enabled
    |--------------------------------------------------------------------------
    |
    | Enable or disable KeyEnv secret loading. Useful for disabling
    | in local development when using .env files.
    |
    */
    'enabled' => env('KEYENV_ENABLED', true),

    /*
    |--------------------------------------------------------------------------
    | Request Timeout
    |--------------------------------------------------------------------------
    |
    | Timeout in seconds for API requests.
    |
    */
    'timeout' => env('KEYENV_TIMEOUT', 30),
];
```

Update the service provider to use the config:

```php
// app/Providers/KeyEnvServiceProvider.php
protected function loadSecrets(): void
{
    if (!config('keyenv.enabled')) {
        return;
    }

    $token = config('keyenv.token');
    $projectId = config('keyenv.project_id');

    if (!$token || !$projectId) {
        return;
    }

    $client = KeyEnv::create($token, config('keyenv.timeout', 30));

    $laravelEnv = app()->environment();
    $keyenvEnv = config("keyenv.environments.{$laravelEnv}", $laravelEnv);

    $client->loadEnv($projectId, $keyenvEnv);
}
```

## Facade Usage

Create a Facade for convenient access to KeyEnv:

```php
// app/Facades/KeyEnv.php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * @method static array exportSecrets(string $projectId, string $environment)
 * @method static array exportSecretsAsArray(string $projectId, string $environment)
 * @method static \KeyEnv\Types\SecretWithValue getSecret(string $projectId, string $environment, string $key)
 * @method static int loadEnv(string $projectId, string $environment)
 *
 * @see \KeyEnv\KeyEnv
 */
class KeyEnv extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \KeyEnv\KeyEnv::class;
    }
}
```

Register an alias in `config/app.php`:

```php
'aliases' => Facade::defaultAliases()->merge([
    'KeyEnv' => App\Facades\KeyEnv::class,
])->toArray(),
```

Now you can use the Facade:

```php
use App\Facades\KeyEnv;

// Get a single secret
$secret = KeyEnv::getSecret(
    config('keyenv.project_id'),
    'production',
    'STRIPE_SECRET_KEY'
);
echo $secret->value;

// Get all secrets as array
$secrets = KeyEnv::exportSecretsAsArray(
    config('keyenv.project_id'),
    'production'
);
```

## Environment-Aware Configuration

### Automatic Environment Detection

```php
// app/Providers/KeyEnvServiceProvider.php
protected function mapLaravelEnv(string $laravelEnv): string
{
    // Check for explicit KeyEnv environment override
    if ($override = env('KEYENV_ENV')) {
        return $override;
    }

    // Check for deployment platform indicators
    if (env('VAPOR_SSM_PATH')) {
        return 'production'; // AWS Lambda via Vapor
    }
    if (env('FORGE_SERVER_ID')) {
        return 'production'; // Laravel Forge
    }
    if (env('RENDER')) {
        return 'production'; // Render
    }

    // Map standard Laravel environments
    return match ($laravelEnv) {
        'local', 'development' => 'development',
        'testing' => 'development',
        'staging' => 'staging',
        'production', 'prod' => 'production',
        default => $laravelEnv,
    };
}
```

### Per-Environment .env Files

Laravel supports environment-specific `.env` files. You can use different KeyEnv configurations:

```bash
# .env.local (development)
KEYENV_TOKEN=env_dev_...
KEYENV_PROJECT=your-project-id
KEYENV_ENV=development

# .env.production
KEYENV_TOKEN=env_prod_...
KEYENV_PROJECT=your-project-id
KEYENV_ENV=production
```

## Using with .env Files

KeyEnv complements Laravel's `.env` pattern. Secrets loaded via `loadEnv()` are available through `env()` and `config()`:

### Bridge Pattern

Load KeyEnv secrets, then use them in your Laravel config:

```php
// config/database.php
return [
    'default' => env('DB_CONNECTION', 'pgsql'),

    'connections' => [
        'pgsql' => [
            'driver' => 'pgsql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '5432'),
            'database' => env('DB_DATABASE', 'laravel'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', ''),
            // These values come from KeyEnv via loadEnv()
        ],
    ],
];
```

```php
// config/mail.php
return [
    'default' => env('MAIL_MAILER', 'smtp'),

    'mailers' => [
        'smtp' => [
            'transport' => 'smtp',
            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
            'port' => env('MAIL_PORT', 587),
            'encryption' => env('MAIL_ENCRYPTION', 'tls'),
            'username' => env('MAIL_USERNAME'),
            'password' => env('MAIL_PASSWORD'),
        ],
    ],
];
```

### Generate .env from KeyEnv

Use the KeyEnv CLI to generate a local `.env` file:

```bash
# Pull secrets to .env file
keyenv pull --env development --output .env

# Or use the PHP SDK
php -r "
require 'vendor/autoload.php';
\$client = KeyEnv\KeyEnv::create(getenv('KEYENV_TOKEN'));
echo \$client->generateEnvFile(getenv('KEYENV_PROJECT'), 'development');
" > .env
```

## Artisan Commands

Create custom Artisan commands for KeyEnv operations:

### keyenv:pull Command

```php
// app/Console/Commands/KeyEnvPull.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use KeyEnv\KeyEnv;

class KeyEnvPull extends Command
{
    protected $signature = 'keyenv:pull
        {--env= : KeyEnv environment (defaults to current APP_ENV)}
        {--output=.env : Output file path}';

    protected $description = 'Pull secrets from KeyEnv and write to .env file';

    public function handle(): int
    {
        $token = config('keyenv.token');
        $projectId = config('keyenv.project_id');

        if (!$token || !$projectId) {
            $this->error('KEYENV_TOKEN and KEYENV_PROJECT must be set');
            return 1;
        }

        $environment = $this->option('env')
            ?? config('keyenv.environments.' . app()->environment())
            ?? app()->environment();

        $output = $this->option('output');

        $this->info("Pulling secrets from KeyEnv ({$environment})...");

        try {
            $client = KeyEnv::create($token);
            $content = $client->generateEnvFile($projectId, $environment);

            file_put_contents(base_path($output), $content);

            $this->info("Secrets written to {$output}");
            return 0;
        } catch (\Exception $e) {
            $this->error("Failed: {$e->getMessage()}");
            return 1;
        }
    }
}
```

### keyenv:env Command

```php
// app/Console/Commands/KeyEnvEnv.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use KeyEnv\KeyEnv;

class KeyEnvEnv extends Command
{
    protected $signature = 'keyenv:env
        {--env= : KeyEnv environment to list}';

    protected $description = 'List available KeyEnv environments';

    public function handle(): int
    {
        $token = config('keyenv.token');
        $projectId = config('keyenv.project_id');

        if (!$token || !$projectId) {
            $this->error('KEYENV_TOKEN and KEYENV_PROJECT must be set');
            return 1;
        }

        try {
            $client = KeyEnv::create($token);
            $environments = $client->listEnvironments($projectId);

            $this->table(
                ['Name', 'Inherits From'],
                collect($environments)->map(fn($env) => [
                    $env->name,
                    $env->inheritsFrom ?? '-',
                ])
            );

            return 0;
        } catch (\Exception $e) {
            $this->error("Failed: {$e->getMessage()}");
            return 1;
        }
    }
}
```

Register commands in `app/Console/Kernel.php` (Laravel 10) or they auto-register in Laravel 11:

```php
protected $commands = [
    Commands\KeyEnvPull::class,
    Commands\KeyEnvEnv::class,
];
```

## Complete Example

Here's a complete setup for a production Laravel application:

### Service Provider

```php
// app/Providers/KeyEnvServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use KeyEnv\KeyEnv;

class KeyEnvServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(__DIR__.'/../../config/keyenv.php', 'keyenv');

        $this->app->singleton(KeyEnv::class, function () {
            $token = config('keyenv.token');
            if (!$token) {
                return null;
            }
            return KeyEnv::create($token, config('keyenv.timeout', 30));
        });

        $this->loadSecrets();
    }

    protected function loadSecrets(): void
    {
        if (!config('keyenv.enabled')) {
            return;
        }

        $token = config('keyenv.token');
        $projectId = config('keyenv.project_id');

        if (!$token || !$projectId) {
            return;
        }

        try {
            $client = KeyEnv::create($token, config('keyenv.timeout', 30));
            $environment = $this->resolveEnvironment();
            $client->loadEnv($projectId, $environment);
        } catch (\Exception $e) {
            // Log but don't crash - allows fallback to .env
            logger()->error('KeyEnv: Failed to load secrets', [
                'error' => $e->getMessage(),
            ]);
        }
    }

    protected function resolveEnvironment(): string
    {
        // Explicit override
        if ($env = env('KEYENV_ENV')) {
            return $env;
        }

        // Map Laravel environment
        $laravelEnv = app()->environment();
        return config("keyenv.environments.{$laravelEnv}", $laravelEnv);
    }

    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../../config/keyenv.php' => config_path('keyenv.php'),
            ], 'keyenv-config');
        }
    }
}
```

### Database Configuration

```php
// config/database.php
return [
    'default' => env('DB_CONNECTION', 'pgsql'),

    'connections' => [
        'pgsql' => [
            'driver' => 'pgsql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '5432'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8',
            'prefix' => '',
            'prefix_indexes' => true,
            'search_path' => 'public',
            'sslmode' => 'prefer',
        ],

        'mysql' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => true,
            'engine' => null,
        ],
    ],

    'redis' => [
        'client' => env('REDIS_CLIENT', 'phpredis'),
        'default' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_DB', '0'),
        ],
        'cache' => [
            'url' => env('REDIS_URL'),
            'host' => env('REDIS_HOST', '127.0.0.1'),
            'password' => env('REDIS_PASSWORD'),
            'port' => env('REDIS_PORT', '6379'),
            'database' => env('REDIS_CACHE_DB', '1'),
        ],
    ],
];
```

### Services Configuration

```php
// config/services.php
return [
    'mailgun' => [
        'domain' => env('MAILGUN_DOMAIN'),
        'secret' => env('MAILGUN_SECRET'),
        'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    ],

    'postmark' => [
        'token' => env('POSTMARK_TOKEN'),
    ],

    'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],

    'stripe' => [
        'key' => env('STRIPE_KEY'),
        'secret' => env('STRIPE_SECRET'),
        'webhook' => [
            'secret' => env('STRIPE_WEBHOOK_SECRET'),
            'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
        ],
    ],

    'pusher' => [
        'app_id' => env('PUSHER_APP_ID'),
        'app_key' => env('PUSHER_APP_KEY'),
        'app_secret' => env('PUSHER_APP_SECRET'),
        'app_cluster' => env('PUSHER_APP_CLUSTER'),
    ],
];
```

## Setting Up Your Deployment

### Required Environment Variables

Set these in your deployment platform (not in KeyEnv):

| Variable | Description |
|----------|-------------|
| `KEYENV_TOKEN` | Your KeyEnv service token |
| `KEYENV_PROJECT` | Your KeyEnv project ID |
| `APP_ENV` | Laravel environment (production, staging, local) |
| `APP_KEY` | Laravel encryption key (optional if in KeyEnv) |

### Secrets to Store in KeyEnv

Store these secrets in KeyEnv for each environment:

| Secret | Description | Example |
|--------|-------------|---------|
| `APP_KEY` | Laravel encryption key | `base64:...` |
| `DB_HOST` | Database host | `db.example.com` |
| `DB_PASSWORD` | Database password | `secure-password` |
| `REDIS_PASSWORD` | Redis password | `redis-password` |
| `MAIL_PASSWORD` | SMTP password | `smtp-password` |
| `STRIPE_SECRET` | Stripe secret key | `sk_live_...` |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook secret | `whsec_...` |
| `AWS_SECRET_ACCESS_KEY` | AWS secret key | `...` |
| `PUSHER_APP_SECRET` | Pusher secret | `...` |

### Generate Laravel App Key

```bash
php artisan key:generate --show
# Copy the output to KeyEnv as APP_KEY
```

## Local Development

For local development, you have several options:

### Option 1: Use KeyEnv in Development

Set `KEYENV_TOKEN` and `KEYENV_PROJECT` in your `.env` file:

```bash
# .env
KEYENV_TOKEN=env_dev_...
KEYENV_PROJECT=your-project-id
KEYENV_ENABLED=true
```

### Option 2: Pull to .env

Use the CLI or Artisan command to generate a local `.env`:

```bash
# Using CLI
keyenv pull --env development --output .env

# Using Artisan
php artisan keyenv:pull --env=development
```

### Option 3: Disable KeyEnv Locally

Use a local `.env` file without KeyEnv:

```bash
# .env
KEYENV_ENABLED=false
DB_HOST=127.0.0.1
DB_PASSWORD=secret
# ... other local secrets
```

## Testing

For tests, disable KeyEnv by setting the environment variable:

```php
// phpunit.xml
<env name="KEYENV_ENABLED" value="false"/>
<env name="APP_ENV" value="testing"/>
```

Or in your test setup:

```php
// tests/TestCase.php
protected function setUp(): void
{
    putenv('KEYENV_ENABLED=false');
    parent::setUp();
}
```

## Troubleshooting

### "KEYENV_TOKEN not set"

Ensure `KEYENV_TOKEN` is set in your deployment platform's environment variables (Forge, Vapor, Render, etc.).

### "Project not found"

Verify `KEYENV_PROJECT` matches your project ID in the KeyEnv dashboard.

### "Environment not found"

Check that the environment name exists in your KeyEnv project. Laravel's `local` environment maps to KeyEnv's `development` by default.

### Config caching issues

When using `php artisan config:cache`, ensure KeyEnv secrets are loaded before caching:

```bash
# In deployment script
php artisan config:clear
# Secrets will be loaded fresh on next request
php artisan config:cache
```

### Secrets not loading

1. Verify `KeyEnvServiceProvider` is registered and runs early
2. Check that `loadEnv()` runs before Laravel reads config
3. Verify the service token has read access to the environment
4. Check for typos in secret key names

### Performance

`loadEnv()` makes a single API call at application boot. Secrets are cached in `$_ENV` for the lifetime of the request. There's no additional overhead after initialization.

For optimal performance with Laravel Octane or similar long-running processes, secrets remain cached in memory.
