| Step | Description |
|---|---|
| 1️⃣ Welcome | Project logo, name, version, description, system overview tiles |
| 2️⃣ Server Requirements | 20+ automated checks — PHP, extensions, memory, permissions |
| 3️⃣ Database Configuration | MySQL/MariaDB setup with AJAX Test Connection |
| 4️⃣ Application Settings | Name, URL, environment, debug mode, timezone, locale |
| 5️⃣ Admin Account | Name, email, username, password with live strength meter |
| 6️⃣ Installation | Real-time SSE progress bar — watch each task run live |
| 7️⃣ Finished | Success summary, action buttons, security reminder, confetti 🎉 |
module.json file in modules/yourframework/ to add support for any PHP projecthtmlspecialchars() everywhereinstall.lock file permanently disables the installer after successcost=12)localStorage| Layer | Technology |
|---|---|
| Backend | Pure PHP 8.0+ · OOP · PSR-4 Autoloading |
| UI Framework | MDB UI Kit v9 (Material Design Bootstrap) |
| Icons | Bootstrap Icons 1.11 |
| Fonts | Inter via Google Fonts |
| Database | MySQL / MariaDB via PDO |
| Real-time | Server-Sent Events (SSE) |
| Server | Apache (with .htaccess) / Nginx |
| Styling | Custom CSS — glassmorphism, CSS variables, animations |
Step 1 — Welcome |
Step 2 — Server Requirements |
Step 3 — Database Configuration |
Step 5 — Admin Account |
Step 6 — Live Installation Progress |
Step 7 — Finished |
mod_rewrite) or Nginx# Clone the repository into your web root
git clone https://github.com/amanprojects-ops/PHP-Installer-Wizard.git LaravelProjectsInstaller
# Navigate to the folder in your browser
# http://localhost/LaravelProjectsInstaller/
The built-in PSR-4 autoloader handles everything automatically — no composer install required.
git clone https://github.com/amanprojects-ops/PHP-Installer-Wizard.git LaravelProjectsInstaller
cd LaravelProjectsInstaller
composer install
Then open:
http://localhost/LaravelProjectsInstaller/
chmod -R 755 storage/
chmod -R 755 storage/logs/
chmod -R 755 storage/sessions/
On Windows (XAMPP) no permission changes are needed.
php-installer-wizard/
│
├── index.php ← Entry point & PSR-4 autoloader
├── composer.json
├── .htaccess ← Apache URL rewriting & security headers
├── install.lock ← Created after successful install (blocks re-run)
│
├── installer/
│ ├── Config/
│ │ ├── installer.php ← Master config (branding, steps, languages)
│ │ └── requirements.php ← Declarative server requirement definitions
│ │
│ ├── Core/
│ │ ├── Application.php ← Front controller + DI container
│ │ ├── Request.php ← HTTP request abstraction
│ │ ├── Response.php ← Redirect / JSON / SSE helpers
│ │ ├── Session.php ← Namespaced session manager + CSRF tokens
│ │ ├── View.php ← PHP template renderer (fetch/render)
│ │ ├── Validator.php ← Fluent input validator
│ │ ├── Logger.php ← Timestamped file logger
│ │ └── Security.php ← XSS, sanitize, key generation, bcrypt
│ │
│ ├── Database/
│ │ ├── Connection.php ← PDO wrapper with testConnection()
│ │ └── DatabaseManager.php ← CREATE DB, importSQL, migrate, seed
│ │
│ ├── Checkers/
│ │ ├── RequirementsChecker.php ← 20+ automated server checks
│ │ └── EnvironmentChecker.php ← Auto-detect project type & parse .env
│ │
│ ├── Modules/ ← Plugin system
│ │ ├── ModuleInterface.php ← Contract all modules must implement
│ │ ├── ModuleLoader.php ← Scans modules/ and resolves active module
│ │ ├── LaravelModule.php ← Laravel tasks (key:generate, migrate, etc.)
│ │ ├── CorePhpModule.php ← CodeIgniter 4 tasks (spark migrate)
│ │ └── GenericModule.php ← Generic PHP fallback
│ │
│ ├── Steps/ ← One class per wizard step
│ │ ├── StepInterface.php
│ │ ├── WelcomeStep.php
│ │ ├── RequirementsStep.php
│ │ ├── DatabaseStep.php
│ │ ├── AppSettingsStep.php
│ │ ├── AdminAccountStep.php
│ │ ├── InstallationStep.php
│ │ └── FinishedStep.php
│ │
│ └── Installation/
│ ├── Task.php ← Named closure wrapper (status/output/elapsed)
│ ├── TaskRunner.php ← SSE streaming task executor
│ └── InstallationLog.php ← JSON installation report writer
│
├── modules/ ← Drop your own modules here
│ ├── laravel/module.json
│ ├── codeigniter/module.json
│ └── generic/module.json
│
├── views/
│ ├── layout.php ← Master HTML shell (MDB UI Kit)
│ ├── partials/
│ │ └── stepper.php
│ └── steps/
│ ├── welcome.php
│ ├── requirements.php
│ ├── database.php
│ ├── app_settings.php
│ ├── admin_account.php
│ ├── installation.php
│ ├── finished.php
│ └── locked.php
│
├── assets/
│ ├── css/installer.css ← Glassmorphism + MDB complement styles
│ ├── js/
│ │ ├── installer.js ← Theme, Toasts, AJAX DB test, MDB init
│ │ ├── progress.js ← SSE EventSource client
│ │ └── password-strength.js ← Strength meter + character checklist
│ └── images/logo.svg
│
├── lang/
│ ├── en.php ← English strings
│ └── fr.php ← French strings
│
└── storage/
├── logs/
│ ├── installer.log ← Runtime log
│ └── installation.json ← Post-install JSON report
└── sessions/
The installer is a standalone subdirectory that lives alongside your PHP project. It runs before your application and sets it up, then locks itself permanently after a successful install.
The key principle: the installer sits next to your project, not inside it.
/htdocs/ ← your web server's document root
├── my-laravel-app/ ← your main Laravel/PHP project
│ ├── app/
│ ├── public/
│ └── ...
│
└── LaravelProjectsInstaller/ ← this installer (sibling directory)
├── index.php
├── installer/
├── modules/
└── ...
You open the installer first → complete all 7 steps → installer locks itself → then you access your main app.
Clone or copy the installer as a sibling to your Laravel project in the web root:
/htdocs/
├── my-laravel-app/ ← existing Laravel project
│ ├── app/
│ ├── bootstrap/
│ ├── config/
│ ├── database/
│ ├── public/
│ ├── resources/
│ ├── routes/
│ ├── storage/
│ ├── artisan ← auto-detected by LaravelModule
│ └── .env.example
│
└── LaravelProjectsInstaller/ ← installer (same level as Laravel root)
├── index.php
├── installer/
│ └── Modules/
│ └── LaravelModule.php
├── modules/
│ └── laravel/
│ └── module.json
└── ...
The LaravelModule auto-detects Laravel by checking:
// installer/Modules/LaravelModule.php
public function detect(): bool
{
return file_exists($this->projectRoot . '/artisan') // artisan file exists
&& is_dir($this->projectRoot . '/app/Http'); // AND app/Http dir exists
}
$this->projectRoot is automatically set to dirname(INSTALLER_ROOT) — i.e., the parent directory of the installer — which is your Laravel root.
| # | Task | What Happens |
|---|---|---|
| 1 | Generate .env | Copies .env.example → .env, populates APP_NAME, APP_ENV, APP_DEBUG, APP_URL, DB_* from wizard form data |
| 2 | Generate App Key | Runs php artisan key:generate --force. Falls back to writing APP_KEY directly if artisan fails |
| 3 | Create Database | Connects without database → runs CREATE DATABASE IF NOT EXISTS with your chosen charset/collation |
| 4 | Run Migrations | Runs php artisan migrate --force and streams output |
| 5 | Seed Database | Runs php artisan db:seed --force |
| 6 | Storage Link | Runs php artisan storage:link |
| 7 | Optimize | Runs config:clear, cache:clear, route:clear, view:clear, optimize in sequence |
| 8 | Lock | Writes install.lock to disable the installer permanently |
Edit installer/Config/installer.php to match your Laravel project:
'project' => [
'name' => 'My Laravel SaaS', // shown on Welcome step
'version' => '2.1.0',
'description' => 'A powerful SaaS built on Laravel.',
'author' => 'Your Company Name',
'login_url' => 'http://localhost/my-laravel-app/public/login',
'docs_url' => 'https://docs.example.com',
],
// Force the Laravel module (skip auto-detection)
'module' => 'laravel',
For a clean URL, configure a virtual host pointing to the installer:
# httpd-vhosts.conf
<VirtualHost *:80>
DocumentRoot "C:/xampp/htdocs/LaravelProjectsInstaller"
ServerName installer.myapp.local
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "C:/xampp/htdocs/my-laravel-app/public"
ServerName myapp.local
</VirtualHost>
Then visit http://installer.myapp.local/ → complete the wizard → visit http://myapp.local/.
storage/ Write Permissions (Linux/Mac)# Run from your Laravel project root (NOT installer root)
chmod -R 775 storage/
chmod -R 775 bootstrap/cache/
chown -R www-data:www-data storage/ bootstrap/cache/
The requirements checker (Step 2) will confirm these are writable before installation proceeds.
After the wizard finishes:
rm -rf /htdocs/LaravelProjectsInstaller/
<Directory "/htdocs/LaravelProjectsInstaller">
Require all denied
</Directory>
/htdocs/
├── my-codeigniter-app/ ← CodeIgniter 4 project
│ ├── app/
│ ├── public/
│ ├── writable/
│ ├── spark ← auto-detected by CorePhpModule
│ └── env ← CI4 env template
│
└── LaravelProjectsInstaller/ ← installer (sibling)
The CorePhpModule detects CodeIgniter 4 by checking for the spark file:
public function detect(): bool
{
return file_exists($this->projectRoot . '/spark');
}
| # | Task | What Happens |
|---|---|---|
| 1 | Set up .env | Copies env → .env, sets CI_ENVIRONMENT, app.baseURL, database.* values from wizard form |
| 2 | Create Database | Runs CREATE DATABASE IF NOT EXISTS via PDO |
| 3 | Run Migrations | Runs php spark migrate --all |
| 4 | Set Permissions | chmod 775 writable/ on Linux/Mac |
| 5 | Lock | Writes install.lock |
// installer/Config/installer.php
'project' => [
'name' => 'My CodeIgniter App',
'version' => '4.5.0',
],
'module' => 'codeigniter', // force module; or leave null for auto-detect
For any custom PHP project that is not Laravel or CodeIgniter.
/htdocs/
├── my-php-app/ ← your custom PHP project
│ ├── index.php
│ ├── config/
│ ├── database.sql ← optional: auto-imported during install
│ └── ...
│
└── LaravelProjectsInstaller/ ← installer (sibling)
If your project has a database schema, place it in the installer root:
LaravelProjectsInstaller/
└── database.sql ← auto-detected and imported by GenericModule
The GenericModule checks for this file and imports it if found:
$sqlFile = INSTALLER_ROOT . '/database.sql';
if (file_exists($sqlFile)) {
$dbManager->importSqlFile($sqlFile);
}
| # | Task | What Happens |
|---|---|---|
| 1 | Create config.php | Writes config.php in the installer root with all app + DB settings |
| 2 | Generate .env | Writes a .env file with APP_KEY, DB_*, APP_* values |
| 3 | Create Storage Dirs | Creates storage/, storage/logs/, storage/cache/, storage/sessions/, storage/uploads/ |
| 4 | Setup Database | Creates the database and imports database.sql if present |
| 5 | Set Permissions | chmod 755 storage/* (Linux/Mac only) |
| 6 | Create Admin | Writes admin.php with bcrypt-hashed password (⚠ delete after setup) |
| 7 | Lock | Writes install.lock |
// installer/Config/installer.php
'module' => 'generic',
After installation, your PHP app can read the generated config.php:
// your-app/bootstrap.php
$config = require __DIR__ . '/../LaravelProjectsInstaller/config.php';
define('DB_HOST', $config['db_host']);
define('DB_NAME', $config['db_name']);
// ...
Or read the .env file:
// Parse the .env using a simple parser
$env = parse_ini_file(__DIR__ . '/../LaravelProjectsInstaller/.env');
$_ENV = array_merge($_ENV, $env);
The installer uses a plugin-based module system. Each module defines the installation tasks for a specific PHP project type.
| Module | Detects | Tasks |
|---|---|---|
laravel |
artisan file + app/Http/ directory |
.env from .env.example, key:generate, create DB, migrate, db:seed, storage:link, optimize |
codeigniter |
spark file |
.env config, create DB, spark migrate, set permissions |
generic |
Fallback for anything else | config.php, .env, storage dirs, DB create, SQL import, admin user, lock |
modules/laravel/module.json → Checks for artisan + app/Http → LaravelModule
modules/codeigniter/module.json → Checks for spark → CorePhpModule
modules/generic/module.json → Always matches as fallback → GenericModule
Add support for any PHP framework by creating two files:
modules/myphp/module.json{
"name": "myphp",
"version": "1.0.0",
"description": "My Custom PHP Framework Installer",
"class": "MyPhp\\Installer",
"detect": ["bootstrap/app.php"]
}
modules/myphp/Installer.php<?php
namespace MyPhp;
use Installer\Modules\ModuleInterface;
use Installer\Installation\Task;
class Installer implements ModuleInterface
{
public function getName(): string { return 'myphp'; }
public function getVersion(): string { return '1.0.0'; }
public function getDescription(): string { return 'My framework installer.'; }
public function getRequirements(): array { return []; }
public function getDefaults(): array { return []; }
public function detect(): bool
{
return file_exists(dirname(INSTALLER_ROOT) . '/bootstrap/app.php');
}
public function getTasks(array $installData): array
{
return [
new Task(
id: 'setup',
name: 'Setting Up Application',
description: 'Running custom setup steps.',
callback: function () use ($installData): string {
// Your custom installation logic here
return 'Setup complete!';
}
),
new Task(
id: 'finalize',
name: 'Finalizing',
description: 'Writing lock file.',
callback: function (): string {
file_put_contents(LOCK_FILE, json_encode([
'installed_at' => date('Y-m-d H:i:s'),
'module' => 'myphp',
]));
return 'Done.';
}
),
];
}
}
That’s it! The ModuleLoader auto-discovers it on the next request.
Edit installer/Config/installer.php to customise:
return [
// Project branding
'project' => [
'name' => 'My PHP Application',
'version' => '2.0.0',
'description' => 'An awesome PHP application.',
'author' => 'Your Name',
'login_url' => 'https://myapp.com/admin/login',
'docs_url' => 'https://myapp.com/docs',
],
// Force a specific module (null = auto-detect)
'module' => null, // or 'laravel', 'codeigniter', 'generic'
// Features
'features' => [
'multi_language' => true,
'theme_switcher' => true,
'license_check' => false,
],
// Default values pre-filled in forms
'defaults' => [
'app_env' => 'production',
'db_host' => '127.0.0.1',
'db_port' => '3306',
'db_charset' => 'utf8mb4',
],
];
| Threat | Mitigation |
|---|---|
| CSRF | Double-submit token validated on every POST |
| XSS | All output passed through htmlspecialchars() |
| SQL Injection | PDO prepared statements everywhere |
| Session Fixation | Session ID regenerated on step advance |
| Re-installation | install.lock file blocks all access after success |
| Password Storage | bcrypt with cost factor 12 |
| File Access | .htaccess blocks direct access to .lock, .json, .log, .env |
| Sensitive Logs | Passwords stripped from installation.json before writing |
| Cookies | httponly, samesite=Lax, secure on HTTPS |
⚠️ After installation: Delete or restrict web access to the installer directory. Even with
install.lock, removing the directory is the safest option.
Currently bundled languages:
| Code | Language |
|---|---|
en |
🇬🇧 English |
fr |
🇫🇷 French |
lang/en.php to lang/de.php (or your language code)installer/Config/installer.php:'languages' => [
'en' => 'English',
'fr' => 'Français',
'de' => 'Deutsch', // ← add here
],
The language switcher in the header appears automatically.
The installer ships with dark mode (default) and light mode, toggled via the ☀️ / 🌙 button in the header. The preference is saved in localStorage.
To change the default theme, edit installer/Config/installer.php:
'default_theme' => 'light', // 'dark' or 'light'
All colours are CSS custom properties in assets/css/installer.css:
:root {
--ins-primary: #6366f1;
--ins-secondary: #8b5cf6;
--ins-accent: #06b6d4;
/* ... */
}
Step 6 uses Server-Sent Events (SSE) for real-time progress updates — no page refresh, no polling loops.
Browser PHP Server
│ │
│── GET ?action=run-install ───────▶│
│ │ [TaskRunner starts]
│◀── event: start ────────────────│ { total: 7 }
│◀── event: task ────────────────│ { status: 'running', progress: 14% }
│◀── event: task ────────────────│ { status: 'done', progress: 28% }
│ ... │
│◀── event: complete ──────────────│ { message: 'Done!', failed: 0 }
│ │ [install.lock created]
│ [Shows "Continue" button] │
The EventSource client lives in assets/js/progress.js.
After every installation run, a detailed JSON report is saved to:
storage/logs/installation.json
Example:
{
"installed_at": "2026-07-09 15:45:32",
"php_version": "8.2.12",
"elapsed_s": 3.847,
"install_data": {
"app_name": "My App",
"app_env": "production",
"db_name": "my_app_db"
},
"tasks": [
{ "id": "create_env", "name": "Generating .env File", "status": "done", "elapsed": 0.012 },
{ "id": "create_db", "name": "Creating Database", "status": "done", "elapsed": 0.231 },
{ "id": "run_migrations", "name": "Running Migrations", "status": "done", "elapsed": 2.914 },
{ "id": "finalize", "name": "Finalizing Installation", "status": "done", "elapsed": 0.005 }
]
}
Passwords are always stripped from this log before writing.
# Check PHP syntax
php -l index.php
php -l installer/Core/Application.php
# Check PHP error log
tail -f storage/logs/installer.log
storage/sessions/ is writable by the web serversession.save_path in php.iniphp_value output_buffering Off in .htaccessproxy_buffering off;max_execution_time ≥ 120 seconds127.0.0.1 vs localhost — use IP on some systems)CREATE DATABASE privilegeCLI php.ini may differ from web server’s php.iniphpinfo() to see the loaded configuration file# Delete the lock file to re-enable the installer
rm install.lock
⚠️ This will allow re-installation — use with caution.
Contributions are welcome and appreciated!
# 1. Fork the repository
# 2. Create your feature branch
git checkout -b feature/amazing-module
# 3. Make your changes and commit
git commit -m "feat: add Symfony installer module"
# 4. Push and open a Pull Request
git push origin feature/amazing-module
This project is licensed under the MIT License — see the LICENSE file for details.
MIT License — Copyright (c) 2026 PHP Installer Wizard Contributors