Skip to main content

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
StateDescription
AvailableAddon files exist, not installed
InstallingInstallation in progress
ActiveInstalled and enabled
InactiveInstalled but disabled
UpdatingUpdate in progress
UninstallingRemoval in progress
RemovedFiles deleted

Addon Tracking

Addon states are tracked in two places:

  1. Database - addons table stores metadata
  2. File - /storage/addons_statuses.json tracks 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

  1. Always backup - Create backups before updates
  2. Version your SQL - Use version numbers in filenames
  3. Test migrations - Ensure rollback works correctly
  4. Clean up on uninstall - Remove all addon data
  5. Fire hooks - Allow other addons to react to lifecycle events
  6. Log operations - Record installation/update/uninstall actions
  7. Handle errors gracefully - Provide meaningful error messages