Laravel

How to Create Installer In Laravel

In this article, we will guide you through the step-by-step process of creating your custom web installer for your web application. If you are building a complex application that requires a lot of initial configuration, it’s considered to develop a web installer.

A web installer is also helpful for the non-tech-savvy users to install your web application on their server. There are many packages built for the same purpose. But in this article, we will guide you through the process of making your own web installer for Laravel.

We will be adding almost all the features in our web installer that are generally needed when installing the Laravel software. Here’s the list of features that we are going to add to our installer:

  • Check Minimum Requirements
  • Setup Database Details
  • Run The Database Migration
  • Create Admin User Account
  • Setup Website Configuration

Steps To Create Installer In Laravel

Here are the steps you need to go through to create your custom Laravel web installer:

Step 1: Setup Your Laravel Application

Create your new Laravel web application if haven’t already using the following command:

laravel new application-name-here

Step 2: Setup Your Web Installer Assets & View Files

  • In this example, we will be using bootstrap to build the setup user interface. So, we will create a folder inside the public folder named “setup” inside which all the bootstrap CSS and JS files will be present.
  • Inside public/setup folder, create two more folder named as css, img.
  • Inside css folder, place bootstrap.min.css file and inside img folder, place your application’s favicon.ico file.
  • Now, create a folder named “setup” inside resources->views. This folder contains all the view files needed for the setup.

After completing these steps, create the following files inside your views/setup folder accordingly:

  • app.blade.php
  • index.blade.php
  • requirements.blade.php
  • database.blade.php
  • account.blade.php
  • config.blade.php
  • complete.blade.php

Paste the following code in those view files accordingly:

Inside app.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{{ config('app.name', 'Custom Installer') }}</title>
    <link rel="icon" href="{{ asset('setup/img/favicon.ico') }}">
    <link href="{{ asset('setup/css/bootstrap.min.css') }}" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    @stack('styles')
  </head>

  <body>
    <main class="main container mt-5">
        <div class="mb-5 h1 text-center text-primary">
            Web Installer
        </div>

        @yield('content')
    </main>
  </body>
  @stack('scripts')
</html>

Inside index.blade.php

@extends('setup.app')
@section('content')
<div class="row justify-content-center">
    <div class="col-12 col-md-8">
        @if($errors->any())
        <div class="card mb-1">
            <div class="card-body text-danger">
                {{$errors->first()}}
            </div>
        </div>
        @endif
        <div class="card">
            <div class="card-header">
                Welcome To Web Installer
            </div>
            <div class="card-body">

                <p>Step By Step Installer</p>

                <ol>
                    <li>Check Minimum Requirements</li>
                    <li>Enter Database Details</li>
                    <li>Setup User Account</li>
                </ol>

                <a href="{{ route('setup.requirements') }}" class="btn btn-primary">
                    Check Minimum Requirements
                </a>
            </div>
        </div>

    </div>
</div>
@endsection

Inside requirements.blade.php

@extends('setup.app')
@section('content')
<div class="row justify-content-center">
    <div class="col-12 col-md-8">

        <div class="card">
            <div class="card-header">
                Minimum Requirements
            </div>
            <div class="card-body">

                <ul>
                    @foreach($checks as $key => $check)
                    <li>
                    @lang('setup.' . $key)
                    @if($check)
                    <i class="fa fa-check text-success"></i>
                    @else
                    <i class="fa fa-close text-danger"></i>
                    @endif
                    </li>
                    @endforeach
                </ul>

                @if($success)
                <a href="{{ route('setup.database') }}" class="btn btn-primary">
                    Setup Database
                </a>
                @endif
            </div>
        </div>

    </div>
</div>
@endsection

Inside database.blade.php

@extends('setup.app')
@section('content')
<div class="row justify-content-center">
    <div class="col-12 col-md-8">
        @if($errors->any())
        <div class="card mb-1">
            <div class="card-body text-danger">
                {{$errors->first()}}
            </div>
        </div>
        @endif
        <div class="card">
            <div class="card-header">
                Enter Database Details
            </div>
            <div class="card-body">
                <form method="POST" action="{{ route('setup.database.submit') }}" autocomplete="off">
                    @csrf
                    <div class="mb-3">
                        <label>Host <span class="text-danger">*</span></label>
                        <input type="text" name="host" class="form-control" value="{{ old('host') ?:'127.0.0.1' }}" placeholder="Enter Database Host">
                        <div class="form-text">Enter Server IP In Case Of Remote Database, 127.0.0.1 Is Default</div>
                    </div>
                    <div class="mb-3">
                        <label>Port <span class="text-danger">*</span></label>
                        <input type="text" name="port" class="form-control" value="{{ old('port') ?:'3306' }}" placeholder="Enter Database Port">
                        <div class="form-text">Enter Database Port. Default Is 3306</div>
                    </div>
                    <div class="mb-3">
                        <label>Database Name <span class="text-danger">*</span></label>
                        <input type="text" name="database" value="{{ old('database')}}" class="form-control" placeholder="Enter Database Name">
                        <div class="form-text">Enter Database Name Here, Create New Database If Haven't Already</div>
                    </div>
                    <div class="mb-3">
                        <label>Database User <span class="text-danger">*</span></label>
                        <input autocomplete="off" type="text" name="user" value="{{ old('user')}}" class="form-control" placeholder="Enter Database User Name Here">
                        <div class="form-text">Enter Database Username Here, Create New User If Haven't Already</div>
                    </div>
                    <div class="mb-3">
                        <label>Database User Password <span class="text-danger">*</span></label>
                        <input autocomplete="new-password" type="password" name="password" value="{{ old('password')}}" class="form-control" placeholder="Enter Database User Password">
                        <div class="form-text">Enter Database Password Here</div>
                    </div>
                    <button type="submit" class="btn btn-primary">Submit Details</button>
                </form>
            </div>
        </div>

    </div>
</div>
@endsection

Inside account.blade.php

@extends('setup.app')
@section('content')
<div class="row justify-content-center">
    <div class="col-12 col-md-8">
        @if($errors->any())
        <div class="card mb-1">
            <div class="card-body text-danger">
                {{$errors->first()}}
            </div>
        </div>
        @endif
        <div class="card">
            <div class="card-header">
                Setup Your User Account
            </div>
            <div class="card-body">
                <form method="POST" action="{{ route('setup.account.submit') }}" autocomplete="off" enctype="multipart/form-data">
                    @csrf
                    <div class="mb-3">
                        <label>Full Name <span class="text-danger">*</span></label>
                        <input type="text" name="name" class="form-control" value="{{ old('name') }}" placeholder="Enter Your Full Name">
                    </div>
                    <div class="mb-3">
                        <label>E-Mail <span class="text-danger">*</span></label>
                        <input type="text" name="email" class="form-control" value="{{ old('email') }}" placeholder="Enter Your E-Mail Address">
                    </div>
                    <div class="mb-3">
                        <label>Profile Pic <span class="text-danger">*</span></label>
                        <input type="file" name="profile_pic" class="form-control" value="{{ old('profile_pic') }}" placeholder="Select Profile Pic">
                    </div>
                    <div class="mb-3">
                        <label>Password <span class="text-danger">*</span></label>
                        <input autocomplete="new-password" type="password" name="password" value="{{ old('password')}}" class="form-control" placeholder="Enter Your Password">
                    </div>
                    <div class="mb-3">
                        <label>Re-Type Password <span class="text-danger">*</span></label>
                        <input autocomplete="new-password" type="password" name="confirm_password" value="{{ old('password')}}" class="form-control" placeholder="Confirm Your Address">
                    </div>
                    <button type="submit" class="btn btn-primary">Create Account</button>
                </form>
            </div>
        </div>

    </div>
</div>
@endsection

Inside: config.blade.php

@extends('setup.app')
@section('content')
<div class="row justify-content-center">
    <div class="col-12 col-md-8">
        @if($errors->any())
        <div class="card mb-1">
            <div class="card-body text-danger">
                {{$errors->first()}}
            </div>
        </div>
        @endif
        <div class="card">
            <div class="card-header">
                Configure Your Website
            </div>
            <div class="card-body">
                <form method="POST" action="{{ route('setup.configuration.submit') }}" autocomplete="off" enctype="multipart/form-data">
                    @csrf
                    <div class="mb-3">
                        <label>Company Name <span class="text-danger">*</span></label>
                        <input type="text" name="config_company_name" class="form-control" value="{{ old('config_company_name') }}" placeholder="Enter Your Company Name">
                    </div>
                    <div class="mb-3">
                        <label>Company Address <span class="text-danger">*</span></label>
                        <textarea name="config_company_address" class="form-control">{{ old('config_company_address') }}</textarea>
                    </div>
                    <div class="mb-3">
                        <label>App Name <span class="text-danger">*</span></label>
                        <input type="text" name="config_app_name" class="form-control" value="{{ old('config_app_name') }}" placeholder="Enter Your App Name">
                    </div>
                    <div class="mb-3">
                        <label>App Currency <span class="text-danger">*</span></label>
                        <select name="config_app_currency" class="form-control">
                            <option value="INR">INR</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>App Language <span class="text-danger">*</span></label>
                        <select name="config_app_lang" class="form-control">
                            <option value="en">EN</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>App Logo <span class="text-danger">*</span></label>
                        <input type="file" name="config_app_logo" class="form-control" placeholder="Select App Logo">
                    </div>
                    <div class="mb-3">
                        <label>App Favicon <span class="text-danger">*</span></label>
                        <input type="file" name="config_app_favicon_icon" class="form-control" placeholder="Select App Favicon">
                    </div>
                    <div class="mb-3">
                        <label>App Timestamp <span class="text-danger">*</span></label>
                        <select name="config_app_timestamp" class="form-control">
                            <option value="Asia/Kolkata">Asia/Kolkata</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>App Color Scheme <span class="text-danger">*</span></label>
                        <select name="config_color_scheme_class" class="form-control">
                            <option value="bg-primary">BG-Primary</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>Right Footer 1 Text <span class="text-danger">*</span></label>
                        <textarea name="config_right_footer_1" class="form-control">{{ old('config_right_footer_1') }}</textarea>
                    </div>
                    <div class="mb-3">
                        <label>Right Footer 2 Text <span class="text-danger">*</span></label>
                        <textarea name="config_right_footer_2" class="form-control">{{ old('config_right_footer_2') }}</textarea>
                    </div>
                    <div class="mb-3">
                        <label>Left Footer 1 Text <span class="text-danger">*</span></label>
                        <textarea name="config_left_footer_1" class="form-control">{{ old('config_left_footer_1') }}</textarea>
                    </div>
                    <div class="mb-3">
                        <label>Left Footer 2 Text <span class="text-danger">*</span></label>
                        <textarea name="config_left_footer_2" class="form-control">{{ old('config_left_footer_2') }}</textarea>
                    </div>
                    <div class="mb-3">
                        <label>If Footer Fixed <span class="text-danger">*</span></label>
                        <select name="config_is_footer_fixed" class="form-control">
                            <option value="fixed-footer">Fixed</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>If Header Fixed <span class="text-danger">*</span></label>
                        <select name="config_is_header_fixed" class="form-control">
                            <option value="fixed-header">Fixed</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>If Sidebar Fixed <span class="text-danger">*</span></label>
                        <select name="config_is_sidebar_fixed" class="form-control">
                            <option value="fixed-sidebar">Fixed</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>If Notification Fixed <span class="text-danger">*</span></label>
                        <select name="config_is_checked_notification" class="form-control">
                            <option value="fixed-notification">Fixed</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>Mailer Protocol <span class="text-danger">*</span></label>
                        <select name="config_mail_mailer" class="form-control">
                            <option value="smtp">SMTP</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>Mail Host <span class="text-danger">*</span></label>
                        <input type="text" name="config_mail_host" class="form-control" value="{{ old('config_mail_host') }}" placeholder="Enter Mail Host">
                    </div>
                    <div class="mb-3">
                        <label>Mail Port <span class="text-danger">*</span></label>
                        <input type="text" name="config_mail_port" class="form-control" value="{{ old('config_mail_port') }}" placeholder="Enter Mail Port">
                    </div>
                    <div class="mb-3">
                        <label>Mail Encryption <span class="text-danger">*</span></label>
                        <select name="config_mail_encryption" class="form-control">
                            <option value="tls">TLS</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label>Mail Username <span class="text-danger">*</span></label>
                        <input type="text" name="config_mail_username" class="form-control" value="{{ old('config_mail_username') }}" placeholder="Enter Mail Username">
                    </div>
                    <div class="mb-3">
                        <label>Mail Password <span class="text-danger">*</span></label>
                        <input type="text" name="config_mail_password" class="form-control" value="{{ old('config_mail_password') }}" placeholder="Enter Mail Password">
                    </div>
                    <div class="mb-3">
                        <label>Mail From <span class="text-danger">*</span></label>
                        <input type="text" name="config_mail_from" class="form-control" value="{{ old('config_mail_from') }}" placeholder="Enter Mail From Address">
                    </div>
                    <button type="submit" class="btn btn-primary">Save Config</button>
                </form>
            </div>
        </div>

    </div>
</div>
@endsection

Inside: complete.blade.php

@extends('setup.app')
@section('content')
<div class="row justify-content-center">
    <div class="col-12 col-md-8">
        @if($errors->any())
        <div class="card mb-1">
            <div class="card-body text-danger">
                {{$errors->first()}}
            </div>
        </div>
        @endif
        <div class="card">
            <div class="card-header">
                Setup Complete
            </div>
            <div class="card-body">
                <p class="text-success">Your Setup Is Now Complete, Launch Your Website Now</p>
                <a class="btn btn-primary" href="{{ url('/') }}">Launch Website</a>
            </div>
        </div>

    </div>
</div>
@endsection

All blade directives required by the Installer are set up successfully. Now we need to create a new Language file.

Step 3: Create a Setup Language File

It’s an optional step but we highly recommend you do this. If your application is multi-lingual, then this is a must.

However, in our example, we are using language files only to show the minimum requirement message.

Navigate to resources -> lang -> en and create a new setup.php file. Then paste the following code:

<?php

/**
 * This Language configs are used when running the setup wizard
 */

return [
    'php_version' => 'PHP version >= 7.4.0',
    'extension_bcmath' => 'PHP Extension: BCMath',
    'extension_ctype' => 'PHP Extension: Ctype',
    'extension_json' => 'PHP Extension: JSON',
    'extension_mbstring' => 'PHP Extension: Mbstring',
    'extension_openssl' => 'PHP Extension: OpenSSL',
    'extension_pdo_mysql' => 'PHP Extension: PDO',
    'extension_tokenizer' => 'PHP Extension: Tokenizer',
    'extension_xml' => 'PHP Extension: XML',
    'env_writable' => '.env file is present and writable',
    'storage_writable' => '/storage and /storage/logs directories are writable',
];

Step 4: Make Setup Middleware

A middleware is required to check whether the setup is complete or not. If the setup is incomplete, the user will be automatically redirected to the setup page.

But if the setup is completed and the user is hitting the setup page again, the user will be redirected to the homepage of the application.

Run the following command to create your custom SetupMiddleware:

php artisan make:middleware SetupMiddleware

Paste the following code inside your middleware. The file will be located inside app/Http/Middleware/SetupMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Artisan;
use Illuminate\Http\Request;

class SetupMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        if(env('APP_KEY') === null || empty(env('APP_KEY'))  && empty(config('app.key'))){
           Artisan::call('key:generate');
           Artisan::call('config:cache');
        }
        $setupStatus = setupStatus();
        if($request->is('setup/*')){
            if($setupStatus){
                return redirect()->route('home');
            }
            return $next($request);
        }
        if(!$setupStatus){
            return redirect()->route('setup.index');
        }
        return $next($request);
    }
}

Step 5: Register Your Middleware

Navigate to app/Http/Kernel.php and inside the protected $middleware array, paste the following line:

\App\Http\Middleware\SetupMiddleware::class,

Now your middleware array should look like this:

    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \App\Http\Middleware\SetupMiddleware::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

Step 6: Create Helper Functions

Now, it’s time to create some helper functions that we will use further. If your project doesn’t have any helpers, create a helper in Laravel as we need them in our project.

Open your helper function file and paste the following functions:

/**
 * Determines if setup is complete or not
 */

function setupStatus()
{
    try {
        $checkComplete = \App\Models\Configuration::where('config', 'setup_complete')->first();
        if (!$checkComplete) {
            return false;
        }
        if ($checkComplete['value'] === '0') {
            return false;
        }
        return true;
    } catch (Exception $e) {
        return false;
    }
}

/**
 * This function is used to save the image in desired location
 */

function saveImage($image, $location)
{
    $imageName = bin2hex(random_bytes(5)) . time() . '.' . $image->extension();
    $image->move(public_path($location), $imageName);
    $url = url($location . '/' . $imageName);
    return $url;
}

Step 7: Create Configuration Model and Migration Files

We need to create a new table inside the database names as configurations. This table holds all the necessary key=>value pairs that are required by the installer.

First, create your model named Configuration by running the command given below:

php artisan make:model Configuration

Paste the following code inside that model file:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Configuration extends Model
{
    use HasFactory;
    
    public $fillable = [
        'config',
        'value',
    ];
}

To create your migration, run the following command:

php artisan make:migration create_configuration_table

Paste the following code inside that migration:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
p
return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('configurations', function (Blueprint $table) {
            $table->id();
            $table->string('config');
            $table->string('value');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('configurations');
    }
};

Now, we also need to populate that table with some default values. So, create another migration by running the following command:

php artisan make:migration add_config_to_configurations_table

Paste the following code inside that migration file:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\Configuration;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Configuration::create([
            'config' => 'setup_complete',
            'value' => 0,
        ]);
        Configuration::create([
            'config' => 'setup_stage',
            'value' => 1,
        ]);
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
    }
};

Step 9: Create Controllers For Laravel Installer

Create a folder named Setup inside app/Http/Controllers as we are going to group all our controllers inside the Setup folder.

Now, run the following commands one by one:

php artisan make:controller Setup/SetupController
php artisan make:controller Setup/AccountController
php artisan make:controller Setup/ConfigurationController

Paste the following code inside each of those controllers:

Code For: SetupController.php

<?php

namespace App\Http\Controllers\Setup;

use App\Http\Controllers\Controller;
use Artisan;
use Config;
use Exception;
use File;
use Illuminate\Http\Request;
use App\Models\Configuration;

class SetupController extends Controller
{
    protected array $dbConfig;

    public function __construct()
    {
        set_time_limit(8000000);
    }

    /**
     * This function is used to display the index of setup
     * @method GET /setup/start/
     * @return Renderable
     */

    public function index()
    {
        return view('setup.index');
    }

    /**
     * This function is used to check for the minimum requirements
     * @method GET /setup/requirements/
     * @return Renderable
     */

    public function requirements()
    {
        [$checks, $success] = $this->checkMinimumRequirements();
        return view('setup.requirements', compact('checks', 'success'));
    }

    /**
     * This function is used to check for the minimum requirements
     * @return Array
     */

    public function checkMinimumRequirements()
    {
        $checks = [
            'php_version' => PHP_VERSION_ID >= 70400,
            'extension_bcmath' => extension_loaded('bcmath'),
            'extension_ctype' => extension_loaded('ctype'),
            'extension_json' => extension_loaded('json'),
            'extension_mbstring' => extension_loaded('mbstring'),
            'extension_openssl' => extension_loaded('openssl'),
            'extension_pdo_mysql' => extension_loaded('pdo_mysql'),
            'extension_tokenizer' => extension_loaded('tokenizer'),
            'extension_xml' => extension_loaded('xml'),
            'env_writable' => File::isWritable(base_path('.env')),
            'storage_writable' => File::isWritable(storage_path()) && File::isWritable(storage_path('logs')),
        ];
        $success = (!in_array(false, $checks, true));
        return [$checks, $success];
    }

    /**
     * This function is used to return the view of database setup
     * @method GET /setup/database/
     * @return Renderable
     */

    public function database()
    {
        return view('setup.database');
    }

    /**
     * This function is used to accept the database submitted values and use them accordingly
     * @method POST /setup/database-submit/
     * @param Request
     * @return Renderable
     */
    public function databaseSubmit(Request $request)
    {
        try {
            $request->validate([
                'host' => 'required|ip',
                'port' => 'required|integer',
                'database' => 'required',
                'user' => 'required',
            ]);
            $this->createDatabaseConnection($request->all());
            $migration = $this->runDatabaseMigration();
            if ($migration !== true) {
                return redirect()->back()->withInput()->withErrors([$migration]);
            }
            $this->changeEnvDatabaseConfig($request->all());
            return view('setup.account');
        } catch (Exception $e) {
            return redirect()->back()->withInput()->withErrors([$e->getMessage()]);
        }
    }

    /**
     * This function is used to create a database connection
     * @param Array of User Submitted Details Of Database
     * @return Response
     */
    public function createDatabaseConnection($details)
    {
        Artisan::call('config:clear');
        $this->dbConfig = config('database.connections.mysql');
        $this->dbConfig['host'] = $details['host'];
        $this->dbConfig['port'] = $details['port'];
        $this->dbConfig['database'] = $details['database'];
        $this->dbConfig['username'] = $details['user'];
        $this->dbConfig['password'] = $details['password'];
        Config::set('database.connections.setup', $this->dbConfig);
    }

    /**
     * This function is used to run the database migration
     */

    public function runDatabaseMigration()
    {
        try {
            Artisan::call('migrate:fresh', [
                '--database' => 'setup',
                '--force' => 'true',
                '--no-interaction' => true,
            ]);
            return true;
        } catch (Exception $e) {
            return $e->getMessage();
        }
    }

        /**
     * This function is used to change the Database Config In ENV File
    */

    public function changeEnvDatabaseConfig($config)
    {
        $this->changeEnvValues('DB_HOST', $config['host']);
        $this->changeEnvValues('DB_PORT', $config['port']);
        $this->changeEnvValues('DB_DATABASE', $config['database']);
        $this->changeEnvValues('DB_USERNAME', $config['user']);
        $this->changeEnvValues('DB_PASSWORD', $config['password']);
    }


    /**
     * This function is used to change the ENV Values
     */

    private function changeEnvValues($key, $value)
    {
        file_put_contents(app()->environmentFilePath(), str_replace(
            $key . '=' . env($key),
            $key . '=' . $value,
            file_get_contents(app()->environmentFilePath())
        ));
    }

        /**
     * This function is used to print the setup complete View
     * @return Renderable
     * @method GET /setup/complete/
     */

    public function setupComplete()
    {
        try{
           $setupStage = Configuration::where('config', 'setup_stage')->firstOrFail();
           if($setupStage['value'] != '3'){
               return redirect()->back()->withInput()->withErrors(['errors' => 'Setup Is Incomplete']);
           }
           $setupStage->update(['value' => '4']);
           Configuration::where('config', 'setup_complete')->firstOrFail()->update(['value' => '1']);;
           return view('setup.complete');
        }catch(Exception $e){
            return redirect()->back()->withErrors([$e->getMessage()]);
        }
    }

}

Code For: AccountController.php

<?php

namespace App\Http\Controllers\Setup;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Configuration;
use Exception;
use App\Models\User;
use Hash;

class AccountController extends Controller
{
    /**
     * This function is used to return View of Account Setup
     * @method GET /setup/account/
     * @return Renderable
     */

    public function account()
    {
        return view('setup.account');
    }

        /**
     * This function is used to create the user Account
     * @param Request
     * @method POST /setup/account-submit/
     * @return Renderable
     */

    public function accountSubmit(Request $request)
    {
        try {
            $request->validate([
                'name' => 'required',
                'email' => 'required|email',
                'profile_pic' => 'required|max:2048|mimes:png,jpg,jpeg,gif',
                'password' => 'required|same:confirm_password',
            ]);
            $profilePic = saveImage($request->profile_pic, 'img/profile');
            User::updateOrCreate([
                'email' => $request->email,
            ],[
                'name' => $request->name,
                'email' => $request->email,
                'profile_pic' => $profilePic,
                'role_id' => 1,
                'password' => Hash::make($request->password),
            ]);
            $stage = Configuration::where('config', 'setup_stage')->firstOrFail()->update(['value' => '2']);
            return redirect()->route('setup.configuration');
        } catch (Exception $e) {
            return redirect()->route('setup.account')->withInput()->withErrors([$e->getMessage()]);
        }
    }
}

Code For: ConfigurationController.php

<?php

namespace App\Http\Controllers\Setup;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Configuration;

class ConfigurationController extends Controller
{
    /**
     * This function is used to return View of Configuration
     * @method GET /setup/configuration/
     * @return Renderable
     */

     public function configuration()
     {
         return view('setup.config');
     }

     /**
      * This function is used to save the configuration values in the database
      * @param Request
      * @return Renderable
      * @method POST /setup/configuration-submit/
      */

    public function configurationSubmit(Request $request)
    {
        try{
            $configurations = $this->processInputs($request);
            $configurations['setup_stage'] = '3';
            foreach($configurations as $key => $config){
                Configuration::updateOrCreate(
                    [
                      'config' => $key
                    ],
                    [
                      'value' => $config
                    ]
                  );
            }
            return redirect()->route('setup.complete');
        }catch(Exception $e){
            return redirect()->route('setup.config')->withInput()->withErrors([$e->getMessage()]);
        }
    }

    /**
     * This function is used to process the inputs
     * It makes the validation first and saves the images etc. to desired path
     * @param Array
     * @return Array
     */

    public function processInputs($request)
    {
        $validated = $this->validateInput($request);
        $logo = saveImage($validated['config_app_logo'], 'img');
        $favicon = saveImage($validated['config_app_favicon_icon'], 'img');
        $validated['config_app_logo'] = $logo;
        $validated['config_app_favicon_icon'] = $favicon;
        return $validated;
    }



    /**
     * This function is used to validate the config submitted input values
     * @param Array
     * @return Array
     */

     public function validateInput($request)
     {
         return $request->validate([
             'config_company_name' => 'required',
             'config_company_address' => 'required',
             'config_app_name' => 'required',
             'config_app_currency' => 'required|in:INR',
             'config_app_lang' => 'required|in:en',
             'config_app_logo' => 'required|max:2048|mimes:png,jpeg,jpg,ico,gif',
             'config_app_favicon_icon' => 'required|max:2048|mimes:png,jpeg,jpg,ico,gif',
             'config_app_timestamp' => 'required|in:Asia/Kolkata',
             'config_color_scheme_class' => 'required|in:bg-primary',
             'config_right_footer_1' => 'required',
             'config_right_footer_2' => 'required',
             'config_left_footer_1' => 'required',
             'config_left_footer_2' => 'required',
             'config_is_footer_fixed' => 'required|in:fixed-footer',
             'config_is_header_fixed' => 'required|in:fixed-header',
             'config_is_sidebar_fixed' => 'required|in:fixed-sidebar',
             'config_is_checked_notification' => 'required|in:fixed-notification',
             'config_mail_mailer' => 'required|in:smtp',
             'config_mail_host' => 'required',
             'config_mail_port' => 'required|integer',
             'config_mail_encryption' => 'required',
             'config_mail_username' => 'required',
             'config_mail_password' => 'required',
             'config_mail_from' => 'required|email',
         ]);
     }
}

Step 10: Register Your Routes

Paste the following route group code inside your web.php file:

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Setup\SetupController;
use App\Http\Controllers\Setup\AccountController;
use App\Http\Controllers\Setup\ConfigurationController;

Route::get('/', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::prefix('setup')->group(function(){
    Route::get('start', [SetupController::class, 'index'])->name('setup.index');
    Route::get('requirements', [SetupController::class, 'requirements'])->name('setup.requirements');
    Route::get('database', [SetupController::class, 'database'])->name('setup.database');
    Route::post('database-submit', [SetupController::class, 'databaseSubmit'])->name('setup.database.submit');
    Route::get('account', [AccountController::class, 'account'])->name('setup.account');
    Route::post('account-submit', [AccountController::class, 'accountSubmit'])->name('setup.account.submit');
    Route::get('configuration', [ConfigurationController::class, 'configuration'])->name('setup.configuration');
    Route::post('configuration-submit', [ConfigurationController::class, 'configurationSubmit'])->name('setup.configuration.submit');
    Route::get('complete', [SetupController::class, 'setupComplete'])->name('setup.complete');
});

We have successfully created our custom Laravel installer for our web application. Now, anyone tries to run your web application need to complete the setup before using the other features of the application.

Steps To Run Laravel Installer

Open your terminal and run the following command:

php artisan serve

Once the web application is running, try opening any URL and it will redirect you to the /setup/start/ page. That’s because Middleware is determining that your setup is incomplete and you need to complete it first. Here are the steps

Step 1: Click on Check Minimum Requirements.

installer welcome page

Step 2: If minimum requirements are met, Setup Database button will appear.

check minimum requirements

Step 3: Click Setup Database button.

Step 4: Enter Database Details such as host, port, and database credentials.

database details

Step 5: Click on Submit button, if database details are right, migration will run. Now, setup your Admin user account.

setup admin user account

Step 6: Set your configuration values and click on Save Config.

create website configuration laravel

Step 7: You’ve successfully completed the setup, now click on Launch Website.

launch website using Laravel web installer

Conclusion

We hope that by following our DIY guide, you will be able to create your custom Laravel Web Installer for your web project. Web Installers are really helpful and it helps us to deploy our Laravel software in easy way.

If you are having some questions regarding the code we’ve written in this article, feel free to drop your comments in the comments section.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button