Database Integration Examples
Working with the database during installation.
#The timing problem
During installation, the database isn't always available:
- Early steps: No database connection yet
- Middle steps: Connection configured but tables don't exist
- Later steps: Migrations run, tables available
Always check database availability before queries.
#Checking database availability
protected function isDatabaseReady(): bool{ try { DB::connection()->getPdo(); return true; } catch (\Exception $e) { return false; }} protected function areTablesReady(): bool{ try { // Try to query a table DB::table('settings')->exists(); return true; } catch (\Exception $e) { return false; }}
#Session-first approach
Always save to session. Save to database if available.
protected function execute(): void{ // Always save to session first $this->storeStepData('app', $this->formData); // Try to save to database if ready if ($this->isDatabaseReady() && $this->areTablesReady()) { $this->saveToDatabase(); }} protected function saveToDatabase(): void{ Setting::updateOrCreate( ['key' => 'app_name'], ['value' => $this->formData['app_name']] ); Setting::updateOrCreate( ['key' => 'app_url'], ['value' => $this->formData['app_url']] );}
#Creating admin user
Create admin user after migrations run.
namespace App\Installer\Steps; use Olakunlevpn\Installer\Steps\BaseStep;use Olakunlevpn\Installer\Concerns\HasFormData;use Olakunlevpn\Installer\Concerns\ManagesSessionData;use App\Models\User;use Illuminate\Support\Facades\Hash; class AccountStep extends BaseStep{ use HasFormData; use ManagesSessionData; protected function mount(): void { $existing = $this->retrieveStepData('account'); if ($existing) { $this->formData = $existing; } } protected function rules(): array { return [ 'formData.name' => 'required|string|max:255', 'formData.email' => 'required|email', 'formData.password' => 'required|min:8', 'formData.password_confirmation' => 'required|same:formData.password', ]; } protected function execute(): void { // Save to session $this->storeStepData('account', [ 'name' => $this->formData['name'], 'email' => $this->formData['email'], 'password' => $this->formData['password'], ]); // Create user if database is ready if ($this->isDatabaseReady() && $this->areTablesReady()) { $this->createAdminUser(); } } protected function createAdminUser(): void { User::updateOrCreate( ['email' => $this->formData['email']], [ 'name' => $this->formData['name'], 'password' => Hash::make($this->formData['password']), 'role' => 'admin', 'email_verified_at' => now(), ] ); } protected function isDatabaseReady(): bool { try { DB::connection()->getPdo(); return true; } catch (\Exception $e) { return false; } } protected function areTablesReady(): bool { try { DB::table('users')->exists(); return true; } catch (\Exception $e) { return false; } }}
#Seeding initial data
Insert default settings after installation.
namespace App\Installer\Steps; use Olakunlevpn\Installer\Steps\BaseStep;use App\Models\Setting;use Illuminate\Support\Facades\DB; class InstallingStep extends BaseStep{ protected function mount(): void { $this->runInstallation(); } protected function runInstallation(): void { try { // Run migrations Artisan::call('migrate', ['--force' => true]); // Seed initial data $this->seedInitialData(); // Move session data to database $this->migrateSessionToDatabase(); $this->redirect(route('installer.completed')); } catch (\Exception $e) { $this->errorMessage = $e->getMessage(); Log::error('Installation failed', ['error' => $e->getMessage()]); } } protected function seedInitialData(): void { // Default settings $defaults = [ 'app_timezone' => 'UTC', 'date_format' => 'Y-m-d', 'time_format' => 'H:i:s', 'items_per_page' => 15, 'maintenance_mode' => false, ]; foreach ($defaults as $key => $value) { Setting::firstOrCreate( ['key' => $key], ['value' => is_bool($value) ? (int)$value : $value] ); } } protected function migrateSessionToDatabase(): void { // App settings $appData = session('installer_step_app'); if ($appData) { Setting::updateOrCreate( ['key' => 'app_name'], ['value' => $appData['app_name']] ); Setting::updateOrCreate( ['key' => 'app_url'], ['value' => $appData['app_url']] ); } // License data $licenseData = session('installer_step_license'); if ($licenseData) { Setting::updateOrCreate( ['key' => 'license_key'], ['value' => $licenseData['purchase_code']] ); Setting::updateOrCreate( ['key' => 'license_email'], ['value' => $licenseData['email']] ); } }}
#Updating .env file
Write database credentials to .env.
namespace App\Installer\Steps; use Olakunlevpn\Installer\Steps\BaseStep;use Illuminate\Support\Facades\File; class DatabaseStep extends BaseStep{ protected function execute(): void { // Save to session $this->storeStepData('database', $this->formData); // Update .env file $this->updateEnvFile(); } protected function updateEnvFile(): void { $envPath = base_path('.env'); if (!File::exists($envPath)) { // Create from .env.example File::copy(base_path('.env.example'), $envPath); } $env = File::get($envPath); $replacements = [ 'DB_CONNECTION' => $this->formData['driver'], 'DB_HOST' => $this->formData['host'], 'DB_PORT' => $this->formData['port'], 'DB_DATABASE' => $this->formData['database'], 'DB_USERNAME' => $this->formData['username'], 'DB_PASSWORD' => $this->formData['password'], ]; foreach ($replacements as $key => $value) { // Escape special characters in password if ($key === 'DB_PASSWORD') { $value = $this->escapeEnvValue($value); } // Replace existing value or append if (preg_match("/^{$key}=/m", $env)) { $env = preg_replace( "/^{$key}=.*/m", "{$key}={$value}", $env ); } else { $env .= "\n{$key}={$value}"; } } File::put($envPath, $env); // Reload config Artisan::call('config:clear'); } protected function escapeEnvValue(string $value): string { // Wrap in quotes if contains special characters if (preg_match('/[^\w.]/', $value)) { return '"' . addslashes($value) . '"'; } return $value; }}
#Migration verification
Check if required columns exist before installation.
namespace App\Installer\Steps; use Olakunlevpn\Installer\Steps\BaseStep;use Olakunlevpn\Installer\Support\MigrationParser; class RequirementsStep extends BaseStep{ protected function validateStep(): void { $this->validate(); if (!$this->verifyMigrations()) { $this->errorMessage = __('requirements::requirements.migration_check_failed'); return; } } protected function verifyMigrations(): bool { $parser = new MigrationParser(); // Check settings table structure if (!$parser->hasMigration('settings')) { $this->errorMessage = 'Settings table migration not found'; return false; } $required = ['key', 'value', 'group']; $missing = $parser->getMissingColumns('settings', $required); if (!empty($missing)) { $this->errorMessage = 'Settings table missing columns: ' . implode(', ', $missing); return false; } // Check users table if (!$parser->hasMigration('users')) { $this->errorMessage = 'Users table migration not found'; return false; } $requiredUserColumns = ['name', 'email', 'password']; $missingUserColumns = $parser->getMissingColumns('users', $requiredUserColumns); if (!empty($missingUserColumns)) { $this->errorMessage = 'Users table missing columns: ' . implode(', ', $missingUserColumns); return false; } return true; }}
#Post-installation data migration
Move all session data to database after installation completes.
namespace App\Listeners; use Olakunlevpn\Installer\Events\InstallationCompleted;use App\Models\Setting;use App\Models\User;use Illuminate\Support\Facades\Hash; class MigrateInstallerData{ public function handle(InstallationCompleted $event): void { $this->migrateAppSettings(); $this->migrateAdminAccount(); $this->migrateLicenseData(); $this->migrateMailSettings(); // Clear installer session $this->clearInstallerSession(); } protected function migrateAppSettings(): void { $data = session('installer_step_app'); if ($data) { Setting::updateOrCreate( ['key' => 'app_name'], ['value' => $data['app_name'], 'group' => 'app'] ); Setting::updateOrCreate( ['key' => 'app_url'], ['value' => $data['app_url'], 'group' => 'app'] ); } } protected function migrateAdminAccount(): void { $data = session('installer_step_account'); if ($data) { User::updateOrCreate( ['email' => $data['email']], [ 'name' => $data['name'], 'password' => Hash::make($data['password']), 'role' => 'admin', 'email_verified_at' => now(), ] ); } } protected function migrateLicenseData(): void { $data = session('installer_step_license'); if ($data) { Setting::updateOrCreate( ['key' => 'license_key'], ['value' => $data['purchase_code'], 'group' => 'license'] ); Setting::updateOrCreate( ['key' => 'license_email'], ['value' => $data['email'], 'group' => 'license'] ); Setting::updateOrCreate( ['key' => 'license_verified_at'], ['value' => now(), 'group' => 'license'] ); } } protected function migrateMailSettings(): void { $data = session('installer_step_mail'); if ($data) { $mailSettings = [ 'mail_driver' => $data['driver'], 'mail_host' => $data['host'], 'mail_port' => $data['port'], 'mail_username' => $data['username'], 'mail_password' => $data['password'], 'mail_encryption' => $data['encryption'], ]; foreach ($mailSettings as $key => $value) { Setting::updateOrCreate( ['key' => $key], ['value' => $value, 'group' => 'mail'] ); } } } protected function clearInstallerSession(): void { $keys = array_keys(session()->all()); foreach ($keys as $key) { if (str_starts_with($key, 'installer_')) { session()->forget($key); } } }}
Register listener:
protected $listen = [ InstallationCompleted::class => [ MigrateInstallerData::class, ],];
#Checking table existence
Before querying a table, verify it exists.
use Illuminate\Support\Facades\Schema; protected function execute(): void{ $this->storeStepData('features', $this->formData); if (Schema::hasTable('feature_flags')) { foreach ($this->formData['features'] as $feature => $enabled) { DB::table('feature_flags')->updateOrInsert( ['name' => $feature], ['enabled' => $enabled, 'updated_at' => now()] ); } }}
#Summary
Database integration best practices:
- Always save to session first
- Check database availability before queries
- Check table existence before inserts
- Update .env for persistent config
- Seed default data after migrations
- Migrate session data after installation
- Use event listeners for cleanup
- Handle exceptions gracefully
The session-first approach ensures data isn't lost if database isn't ready.