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. Create Public Asset Symlink
A symlink is created so the addon's static assets (CSS, JS, images) are accessible from the browser:
// Create symlink: public/Addons/EmailValidator → Addons/EmailValidator/public
$addonPublicDir = base_path('Addons/EmailValidator/public');
$publicAddonsPath = public_path('Addons');
$symlinkTarget = $publicAddonsPath . '/EmailValidator';
if (is_dir($addonPublicDir) && !file_exists($symlinkTarget)) {
// Ensure parent directory exists
if (!is_dir($publicAddonsPath)) {
mkdir($publicAddonsPath, 0755, true);
}
symlink($addonPublicDir, $symlinkTarget);
}
This maps Addons/EmailValidator/public/css/addon.css to the URL /Addons/EmailValidator/css/addon.css.
5. 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,
]);
6. 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. The function takes no parameters and must return an array:
// functions.php
<?php
/**
* Called during addon installation.
* The system calls install_addon() with no arguments.
*
* @return array Must contain 'success' key. Return ['success' => false, 'message' => '...'] to abort.
*/
function install_addon(): array
{
try {
// Optional: check dependencies
$checkDep = emailvalidator_checkDependency();
if ($checkDep !== 'success') {
return ['success' => false, 'message' => $checkDep];
}
// Optional: check license
$checkLic = emailvalidator_checkLicense();
if ($checkLic !== 'success') {
return ['success' => false, 'message' => $checkLic];
}
// Any custom setup logic (seed data, create directories, etc.)
// Note: migrations and module enabling are handled by the system automatically
return ['success' => true, 'message' => 'Installed successfully'];
} catch (\Exception $e) {
return ['success' => false, 'message' => $e->getMessage()];
}
}
/**
* Check addon dependencies.
* Return "success" or an error message string.
*/
function emailvalidator_checkDependency($request = null): string
{
// Check PHP extensions, external services, etc.
return "success";
}
/**
* Check addon license.
* Return "success" or an error message string.
*/
function emailvalidator_checkLicense(): mixed
{
return "success";
}
After install_addon() returns success, the system automatically:
- Creates/updates the addon record in the
addonstable - Enables the module (
module:enable) - Runs database migrations
- Publishes translations and config
- Creates the public asset symlink
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
The update_addon function takes no parameters and must return an array. On success, it should include the update_url where the system can download the new version:
// functions.php
/**
* Called before updating the addon.
* The system calls update_addon() with no arguments.
*
* @return array On success: ['success' => true, 'update_url' => '...']. On failure: ['success' => false, 'message' => '...']
*/
function update_addon(): array
{
try {
// Optional: verify license and dependencies before allowing update
$checkLic = emailvalidator_checkLicense();
$checkDep = emailvalidator_checkDependency();
if ($checkDep !== 'success' || $checkLic !== 'success') {
return ['success' => false, 'message' => $checkDep !== 'success' ? $checkDep : $checkLic];
}
// Return the URL where the system can download the update package
return [
'success' => true,
'update_url' => 'https://yoursite.com/addons/emailvalidator/update.zip',
];
} catch (\Exception $e) {
return ['success' => false, 'message' => $e->getMessage()];
}
}
After update_addon() returns success, the system downloads the update package from update_url, extracts it, runs new migrations, and updates the version record.
Activation/Deactivation
Activate Addon
// Enable module
Artisan::call('module:enable', ['module' => 'EmailValidator']);
// Update status
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'active',
]);
The symlink is created during installation, not activation. Activate/deactivate only toggles the module's enabled state.
Deactivate Addon
// Disable module
Artisan::call('module:disable', ['module' => 'EmailValidator']);
// Update status
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'inactive',
]);
Uninstallation
Uninstall (Keep Files)
// Rollback migrations
Artisan::call('migrate:rollback', [
'--path' => 'Addons/EmailValidator/Database/Migrations',
'--step' => 9999,
'--force' => true,
]);
// Disable module
Artisan::call('module:disable', ['module' => 'EmailValidator']);
// Remove public asset symlink
$symlinkPath = public_path('Addons/EmailValidator');
if (is_link($symlinkPath)) {
unlink($symlinkPath);
}
// Update status
Addon::where('install_dir', 'EmailValidator')->update([
'status' => 'available',
]);
Remove (Delete Files)
When an addon is removed via the admin panel, the system:
- Rolls back all migrations
- Disables the module
- Removes the public asset symlink
- Deletes the addon database record
- Deletes the addon directory from
Addons/
Uninstall Behavior
The system handles uninstallation automatically -- there is no uninstall_addon() function to implement. When the admin uninstalls your addon, the system:
- Rolls back all addon migrations (
migrate:rollback) - Removes the public asset symlink (
public/Addons/YourAddonName) - Disables the module (
module:disable) - Updates the addon status to
available
If you need cleanup beyond migration rollback (e.g., removing files from storage), use the Settings/uninstall/uninstall.sql script for database cleanup.
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