Addon Lifecycle
This guide covers how addons are installed, updated, activated, deactivated, and removed from Mumara Campaigns.
Lifecycle States
Addons go through various states during their lifecycle:
Available → Installing → Installed/Active → Updating → Active
↓
Deactivating → Inactive → Uninstalling → Removed
| State | Description |
|---|---|
| Available | Addon files exist, not installed |
| Installing | Installation in progress |
| Active | Installed and enabled |
| Inactive | Installed but disabled |
| Updating | Update in progress |
| Uninstalling | Removal in progress |
| Removed | Files deleted |
Addon Tracking
Addon states are tracked in two places:
- Database -
addonstable stores metadata - File -
/storage/addons_statuses.jsontracks enabled/disabled
Database Schema
CREATE TABLE `addons` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL,
`type` VARCHAR(50) DEFAULT NULL,
`vendor` VARCHAR(100) DEFAULT NULL,
`installed_version` VARCHAR(20) DEFAULT NULL,
`available_version` VARCHAR(20) DEFAULT NULL,
`status` ENUM('available', 'installed', 'active', 'inactive') DEFAULT 'available',
`error` TEXT DEFAULT NULL,
`install_dir` VARCHAR(100) DEFAULT NULL,
`license_key` VARCHAR(255) DEFAULT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Installation Process
When an addon is installed:
1. Pre-Installation Checks
// Check dependencies
$missing = check_addon_dependencies('EmailValidator');
if (!empty($missing)) {
throw new \Exception('Missing dependencies: ' . implode(', ', $missing));
}
// Check license (if applicable)
if ($addon->license_type !== 'free') {
$valid = AddonLicense::verify($addon->license_key, $addon->name);
if (!$valid) {
throw new \Exception('Invalid license key');
}
}
2. Database Setup
// Run migrations
Artisan::call('module:migrate', ['module' => 'EmailValidator']);
// Execute install SQL files
$installDir = addonInstallDir('EmailValidator');
foreach (glob($installDir . '/*.sql') as $sqlFile) {
$sql = file_get_contents($sqlFile);
DB::unprepared($sql);
}
3. Configuration Publishing
// Publish config
Artisan::call('module:publish-config', ['module' => 'EmailValidator']);
// Publish translations
Artisan::call('module:publish-translation', ['module' => 'EmailValidator']);
4. Enable Addon
// Enable in Laravel Modules
Artisan::call('module:enable', ['module' => 'EmailValidator']);
// Update database record
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'active',
'installed_version' => $addon->version,
]);
5. Clear Caches
Artisan::call('cache:clear');
Artisan::call('config:clear');
Artisan::call('view:clear');
Artisan::call('route:clear');
Install Function
Implement the install_addon function in your addon's functions.php:
// functions.php
<?php
function install_addon($addon_name)
{
try {
// Run installation SQL scripts
$installPath = addonInstallDir($addon_name);
if (is_dir($installPath)) {
$sqlFiles = glob($installPath . '/v*.sql');
sort($sqlFiles);
foreach ($sqlFiles as $file) {
$sql = file_get_contents($file);
\DB::unprepared($sql);
}
}
// Create default settings
$settings = [
['key' => 'enabled', 'value' => 'true'],
['key' => 'installed_at', 'value' => now()->toIso8601String()],
['key' => 'default_validation_level', 'value' => 'standard'],
];
foreach ($settings as $setting) {
\DB::table('email_validator_settings')->insertOrIgnore($setting);
}
// Create default data
$this->seedDefaultData();
// Fire installation hook
run_hook_in_background('EmailValidatorInstalled', [
'addon' => $addon_name,
'version' => config('emailvalidator.version'),
]);
return true;
} catch (\Exception $e) {
\Log::error("Addon installation failed: {$addon_name}", [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return false;
}
}
function seedDefaultData()
{
// Seed disposable domains
$domains = [
'mailinator.com', 'guerrillamail.com', '10minutemail.com',
'tempmail.com', 'throwaway.email', 'getnada.com',
];
foreach ($domains as $domain) {
\DB::table('email_validator_disposable_domains')->insertOrIgnore([
'domain' => $domain,
'created_at' => now(),
]);
}
}
Update Process
When an addon is updated:
1. Download Update
// Get update from configured URL
$updateUrl = $addon->update_url;
$zipPath = storage_path('app/temp/addon-update.zip');
file_put_contents($zipPath, file_get_contents($updateUrl));
2. Backup Current Version
$addonPath = addonDir('EmailValidator');
$backupPath = storage_path('app/backups/EmailValidator-' . $currentVersion);
// Create backup
File::copyDirectory($addonPath, $backupPath);
3. Extract and Replace
$zip = new ZipArchive();
$zip->open($zipPath);
$zip->extractTo($addonPath);
$zip->close();
4. Run Update Scripts
// Run new migrations
Artisan::call('module:migrate', ['module' => 'EmailValidator']);
// Run update SQL (only files newer than current version)
$installDir = addonInstallDir('EmailValidator');
foreach (glob($installDir . '/v*.sql') as $file) {
preg_match('/v([\d.]+)\.sql$/', $file, $matches);
$fileVersion = $matches[1] ?? '0';
if (version_compare($fileVersion, $currentVersion, '>')) {
$sql = file_get_contents($file);
DB::unprepared($sql);
}
}
5. Update Database Record
Addon::where('install_dir', 'EmailValidator')->update([
'installed_version' => $newVersion,
'available_version' => $newVersion,
]);
Update Function
// functions.php
function update_addon($addon_name, $currentVersion = null, $newVersion = null)
{
try {
// Get current version from database if not provided
if (!$currentVersion) {
$addon = \App\Models\Addon::where('install_dir', $addon_name)->first();
$currentVersion = $addon->installed_version ?? '0.0.0';
}
// Run migrations
\Artisan::call('module:migrate', ['module' => $addon_name]);
// Run version-specific SQL updates
$installDir = addonInstallDir($addon_name);
if (is_dir($installDir)) {
$sqlFiles = glob($installDir . '/v*.sql');
sort($sqlFiles);
foreach ($sqlFiles as $file) {
preg_match('/v([\d.]+)\.sql$/', $file, $matches);
$fileVersion = $matches[1] ?? '0';
// Only run if file version is greater than current
if (version_compare($fileVersion, $currentVersion, '>')) {
$sql = file_get_contents($file);
\DB::unprepared($sql);
\Log::info("Executed update SQL: {$file}");
}
}
}
// Run version-specific migrations
$this->runVersionMigrations($addon_name, $currentVersion, $newVersion);
// Clear caches
\Artisan::call('cache:clear');
\Artisan::call('config:clear');
// Fire update hook
run_hook_in_background('EmailValidatorUpdated', [
'addon' => $addon_name,
'from_version' => $currentVersion,
'to_version' => $newVersion,
]);
return true;
} catch (\Exception $e) {
\Log::error("Addon update failed: {$addon_name}", [
'error' => $e->getMessage(),
]);
return false;
}
}
function runVersionMigrations($addon_name, $fromVersion, $toVersion)
{
// Run version-specific PHP migrations
$migrationsPath = module_path($addon_name, 'Database/Updates');
if (is_dir($migrationsPath)) {
$files = glob($migrationsPath . '/*.php');
foreach ($files as $file) {
preg_match('/v([\d.]+)\.php$/', $file, $matches);
$fileVersion = $matches[1] ?? '0';
if (version_compare($fileVersion, $fromVersion, '>') &&
version_compare($fileVersion, $toVersion, '<=')) {
require_once $file;
}
}
}
}
Activation/Deactivation
Activate Addon
// Enable module
Artisan::call('module:enable', ['module' => 'EmailValidator']);
// Update status
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'active',
]);
// Fire hook
listen_hook('AddonActivated', ['addon' => 'EmailValidator']);
Deactivate Addon
// Disable module
Artisan::call('module:disable', ['module' => 'EmailValidator']);
// Update status
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'inactive',
]);
// Fire hook
listen_hook('AddonDeactivated', ['addon' => 'EmailValidator']);
Uninstallation
Uninstall (Keep Files)
// Rollback migrations
Artisan::call('module:migrate-reset', ['module' => 'EmailValidator']);
// Run uninstall SQL
$uninstallFile = addonUnInstallDir('EmailValidator') . '/uninstall.sql';
if (file_exists($uninstallFile)) {
DB::unprepared(file_get_contents($uninstallFile));
}
// Disable module
Artisan::call('module:disable', ['module' => 'EmailValidator']);
// Update status
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'available',
'installed_version' => null,
]);
Remove (Delete Files)
// Uninstall first
uninstall_addon('EmailValidator');
// Delete addon directory
$addonPath = addonDir('EmailValidator');
File::deleteDirectory($addonPath);
// Remove from database
Addon::where('install_dir', 'EmailValidator')->delete();
Uninstall Function
// functions.php
function uninstall_addon($addon_name)
{
try {
// Fire pre-uninstall hook
listen_hook('BeforeAddonUninstall', ['addon' => $addon_name]);
// Run uninstall SQL
$uninstallDir = addonUnInstallDir($addon_name);
$uninstallFile = $uninstallDir . '/uninstall.sql';
if (file_exists($uninstallFile)) {
$sql = file_get_contents($uninstallFile);
\DB::unprepared($sql);
}
// Rollback migrations
\Artisan::call('module:migrate-reset', ['module' => $addon_name]);
// Clean up addon-specific data
$this->cleanupAddonData($addon_name);
// Fire post-uninstall hook
run_hook_in_background('AddonUninstalled', ['addon' => $addon_name]);
return true;
} catch (\Exception $e) {
\Log::error("Addon uninstall failed: {$addon_name}", [
'error' => $e->getMessage(),
]);
return false;
}
}
function cleanupAddonData($addon_name)
{
// Remove addon settings
\DB::table('addon_settings')
->where('addon', strtolower($addon_name))
->delete();
// Remove cached data
\Cache::forget("addon_{$addon_name}_config");
// Clean up storage files
$storagePath = storage_path("app/addons/{$addon_name}");
if (is_dir($storagePath)) {
\File::deleteDirectory($storagePath);
}
}
SQL Scripts
Install SQL (Settings/install/v1.0.sql)
-- Version 1.0 Installation
CREATE TABLE IF NOT EXISTS `email_validator_results` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`user_id` INT UNSIGNED NOT NULL,
`email` VARCHAR(320) NOT NULL,
`status` ENUM('valid', 'invalid', 'unknown', 'pending') DEFAULT 'pending',
`score` TINYINT UNSIGNED DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_user_id` (`user_id`),
INDEX `idx_email` (`email`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `email_validator_settings` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`key` VARCHAR(100) NOT NULL,
`value` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `key_unique` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Update SQL (Settings/install/v1.1.sql)
-- Version 1.1 Updates
ALTER TABLE `email_validator_results`
ADD COLUMN IF NOT EXISTS `mx_found` TINYINT(1) DEFAULT 0 AFTER `score`,
ADD COLUMN IF NOT EXISTS `is_disposable` TINYINT(1) DEFAULT 0 AFTER `mx_found`;
CREATE TABLE IF NOT EXISTS `email_validator_disposable_domains` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`domain` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `domain_unique` (`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Uninstall SQL (Settings/uninstall/uninstall.sql)
-- Uninstall Script
DROP TABLE IF EXISTS `email_validator_results`;
DROP TABLE IF EXISTS `email_validator_settings`;
DROP TABLE IF EXISTS `email_validator_disposable_domains`;
DROP TABLE IF EXISTS `email_validator_batches`;
-- Remove columns added to users table
ALTER TABLE `users`
DROP COLUMN IF EXISTS `email_validation_credits`,
DROP COLUMN IF EXISTS `total_validations`;
Version Checking
Check for updates from remote server:
// Check for new version
$currentVersion = config('emailvalidator.version');
$checkUrl = config('emailvalidator.new_version_url');
$response = Http::get($checkUrl);
$data = $response->json();
if (version_compare($data['version'], $currentVersion, '>')) {
// Update available
Addon::where('install_dir', 'EmailValidator')->update([
'available_version' => $data['version'],
]);
}
Best Practices
- Always backup - Create backups before updates
- Version your SQL - Use version numbers in filenames
- Test migrations - Ensure rollback works correctly
- Clean up on uninstall - Remove all addon data
- Fire hooks - Allow other addons to react to lifecycle events
- Log operations - Record installation/update/uninstall actions
- Handle errors gracefully - Provide meaningful error messages