AtoM AHG Framework - Technical Architecture¶
For Technical Discussion with Artifactual¶
Prepared by: The Archive and Heritage Group
Date: January 2026
1. Executive Summary¶
The AtoM AHG Framework is a non-invasive modernization layer that: - Integrates Laravel Query Builder into AtoM 2.10 without modifying core files - Provides database-driven plugin management replacing hardcoded configurations - Maintains 100% backward compatibility with existing AtoM functionality - Targets GLAM institutions with South African regulatory compliance (POPIA, NARSSA, PAIA, GRAP 103)
Key Principle: We enhance AtoM, we don't fork it.
2. Architectural Overview¶
┌─────────────────────────────────────────────────────────────────────────┐
│ USER INTERFACE │
│ (Bootstrap 5 via ahgThemeB5Plugin) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ AtoM 2.10 PRESENTATION LAYER │ │
│ │ (Symfony 1.x Controllers, Actions, Templates) │ │
│ │ ↓ UNCHANGED ↓ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ INTEGRATION POINT │ │
│ │ ProjectConfiguration.class.php │ │
│ │ loadPluginsFromDatabase() ← NEW FUNCTION │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┴────────────────────┐ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ SYMFONY 1.x ORM │ │ atom-framework │ │
│ │ (Propel) │ │ (Laravel Query │ │
│ │ Core AtoM Data │ │ Builder) │ │
│ │ │ │ Extension Data │ │
│ └──────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │
│ └────────────────┬───────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ MySQL 8 DATABASE │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Core AtoM │ │ AHG Extensions │ │ │
│ │ │ Tables │ │ Tables (atom_*) │ │ │
│ │ │ (unchanged) │ │ │ │ │
│ │ └─────────────────┘ └─────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3. How the Framework Connects to AtoM¶
3.1 Bootstrap Integration Point¶
The framework bootstraps via a single function added to AtoM's config:
// /config/ProjectConfiguration.class.php
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
// 1. Load core Symfony plugins (unchanged)
$corePlugins = array(
'sfWebBrowserPlugin',
'sfThumbnailPlugin',
// ... standard AtoM plugins
);
// 2. NEW: Load database-driven plugins
$this->loadPluginsFromDatabase($corePlugins);
}
// NEW FUNCTION - Only addition to AtoM config
protected function loadPluginsFromDatabase($corePlugins)
{
// Initialize Laravel Query Builder
require_once dirname(__FILE__) . '/../atom-framework/bootstrap.php';
// Query atom_plugin table
$dbPlugins = DB::table('atom_plugin')
->where('is_enabled', 1)
->orderBy('load_order')
->pluck('name')
->toArray();
// Merge and enable
$allPlugins = array_unique(array_merge($corePlugins, $dbPlugins));
$this->enablePlugins($allPlugins);
}
}
3.2 Framework Bootstrap Sequence¶
Request → index.php → ProjectConfiguration::setup()
│
├── 1. Standard Symfony init
│
├── 2. loadPluginsFromDatabase()
│ │
│ ├── require atom-framework/bootstrap.php
│ │ │
│ │ ├── Composer autoload
│ │ ├── Initialize Capsule (Laravel DB)
│ │ └── Connect to MySQL
│ │
│ ├── Query atom_plugin table
│ └── Return enabled plugins
│
├── 3. enablePlugins($merged)
│
└── 4. Continue Symfony lifecycle
4. Database Architecture¶
4.1 Plugin Management Table¶
CREATE TABLE atom_plugin (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE, -- Plugin class name
class_name VARCHAR(255) NOT NULL, -- Configuration class
version VARCHAR(50),
description TEXT,
category VARCHAR(100) DEFAULT 'general', -- theme|ahg|sector|utility
is_enabled TINYINT(1) DEFAULT 0,
is_core TINYINT(1) DEFAULT 0, -- Cannot be disabled
is_locked TINYINT(1) DEFAULT 0, -- Cannot be modified
load_order INT DEFAULT 100, -- Plugin loading sequence
plugin_path VARCHAR(500),
settings JSON, -- Per-plugin configuration
enabled_at TIMESTAMP,
disabled_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
4.2 Dual-ORM Strategy¶
┌─────────────────────────────────────────────────────────────────────────┐
│ DATA ACCESS PATTERNS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ PROPEL (Symfony 1.x ORM) │ LARAVEL QUERY BUILDER │
│ ───────────────────────── │ ─────────────────────── │
│ │ │
│ • Core AtoM entities: │ • Extension tables: │
│ - QubitInformationObject │ - atom_plugin │
│ - QubitActor │ - atom_landing_page │
│ - QubitTerm │ - atom_security_clearance │
│ - QubitRepository │ - atom_researcher │
│ - QubitDigitalObject │ - atom_audit_log │
│ │ │
│ • Why Propel for core: │ • Why Laravel for extensions: │
│ - Maintains AtoM compatibility │ - Modern fluent syntax │
│ - Existing relationships work │ - Easier testing │
│ - No migration risk │ - Repository pattern support │
│ │ - Collection helpers │
│ │ │
│ EXCEPTION: Plugin Manager uses │ │
│ PDO directly due to autoloader │ │
│ conflicts during Symfony boot │ │
│ │ │
└─────────────────────────────────────────────────────────────────────────┘
5. Plugin Architecture¶
5.1 Directory Structure¶
/usr/share/nginx/archive/
├── atom-framework/ # LAYER 1: Core Framework
│ ├── bootstrap.php # Laravel initialization
│ ├── composer.json # Dependencies
│ ├── bin/
│ │ ├── install # Installation script
│ │ ├── atom # CLI entry point
│ │ └── release # Version management
│ ├── config/
│ │ └── ProjectConfiguration.class.php.template
│ ├── database/
│ │ └── install.sql # Schema definitions
│ └── src/
│ ├── Extensions/
│ │ ├── ExtensionManager.php # Plugin management service
│ │ └── ExtensionProtection.php
│ ├── Repositories/ # Base repository classes
│ ├── Services/ # Business logic services
│ └── Helpers/ # Utility classes
│
├── atom-ahg-plugins/ # LAYER 2: Plugin Source
│ ├── ahgThemeB5Plugin/ # Bootstrap 5 theme (LOCKED)
│ ├── ahgSecurityClearancePlugin/ # Security system (LOCKED)
│ ├── ahgLibraryPlugin/ # Library sector features
│ ├── ahgLandingPagePlugin/ # Landing page builder
│ └── [26+ additional plugins]
│
├── plugins/ # Symlinks for Symfony
│ ├── ahgThemeB5Plugin -> ../atom-ahg-plugins/ahgThemeB5Plugin
│ ├── ahgLibraryPlugin -> ../atom-ahg-plugins/ahgLibraryPlugin
│ └── [symlinks for all enabled plugins]
│
└── config/
└── ProjectConfiguration.class.php # Modified with loadPluginsFromDatabase()
5.2 Plugin Loading Flow¶
┌─────────────────────────────────────────────────────────────────────────┐
│ PLUGIN LOADING SEQUENCE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Symfony Initialization │
│ └── ProjectConfiguration::setup() │
│ │
│ 2. Core Plugins (Hardcoded) │
│ └── $corePlugins = ['sfWebBrowserPlugin', 'sfThumbnailPlugin', ...] │
│ │
│ 3. Database Query (NEW) │
│ └── SELECT name FROM atom_plugin │
│ WHERE is_enabled = 1 │
│ ORDER BY load_order │
│ │
│ 4. Merge Arrays │
│ └── $allPlugins = array_unique(merge($core, $db)) │
│ │
│ 5. Enable Plugins │
│ └── $this->enablePlugins($allPlugins) │
│ └── Symfony loads each plugin's Configuration class │
│ └── Routes registered │
│ └── Templates available │
│ └── Assets accessible │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6. CLI Interface¶
6.1 Command Structure¶
# Extension Management
php bin/atom extension:discover # Scan and list available extensions
php bin/atom extension:enable <name> # Enable a plugin
php bin/atom extension:disable <name># Disable a plugin (if not locked)
php bin/atom extension:list # Show enabled/disabled status
# Framework Management
php bin/atom framework:install # Initial setup
php bin/atom framework:update # Pull latest changes
php bin/atom framework:version # Display version info
# Version Release (Development)
./bin/release patch "message" # Bump patch version
./bin/release minor "message" # Bump minor version
6.2 Extension Enable Process¶
php bin/atom extension:enable ahgLibraryPlugin
│
├── 1. Validate plugin exists in atom-ahg-plugins/
│
├── 2. Check dependencies (extension.json)
│
├── 3. Create symlink: plugins/ -> atom-ahg-plugins/
│
├── 4. UPDATE atom_plugin SET is_enabled = 1
│
├── 5. Run plugin's install task (if exists)
│
├── 6. Load data files (taxonomy terms, etc.)
│
├── 7. Clear Symfony cache
│
└── 8. Output success message
7. What We Don't Modify¶
7.1 Core AtoM Integrity¶
┌─────────────────────────────────────────────────────────────────────────┐
│ UNTOUCHED COMPONENTS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ DATABASE SCHEMA (Never Modified) │
│ ─────────────────────────────── │
│ • object • slug │
│ • information_object • information_object_i18n │
│ • actor • actor_i18n │
│ • term • term_i18n │
│ • taxonomy • repository │
│ • digital_object • physical_object │
│ • relation • event │
│ • user • acl_* │
│ │
│ PHP FILES (Never Patched) │
│ ──────────────────────── │
│ • lib/QubitInformationObject.php │
│ • lib/QubitActor.php │
│ • lib/model/map/*.php │
│ • lib/model/om/*.php │
│ • apps/qubit/modules/* (core modules) │
│ │
│ CONFIGURATION (Template Replacement Only) │
│ ───────────────────────────────────────── │
│ • ProjectConfiguration.class.php │
│ └── Template REPLACES entire file (not patched) │
│ └── Contains loadPluginsFromDatabase() addition │
│ │
│ ROUTING (Preserved) │
│ ───────────────── │
│ • All core AtoM routes work unchanged │
│ • Extensions add NEW routes only │
│ │
└─────────────────────────────────────────────────────────────────────────┘
8. Repository Pattern¶
8.1 Extension Repository Example¶
<?php
// atom-framework/src/Repositories/BaseRepository.php
namespace AtomExtensions\Repositories;
use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Support\Collection;
abstract class BaseRepository
{
protected string $table;
protected string $primaryKey = 'id';
public function find(int $id): ?array
{
return DB::table($this->table)
->where($this->primaryKey, $id)
->first();
}
public function findWhere(array $conditions): Collection
{
$query = DB::table($this->table);
foreach ($conditions as $column => $value) {
$query->where($column, $value);
}
return $query->get();
}
public function create(array $data): int
{
return DB::table($this->table)->insertGetId($data);
}
public function update(int $id, array $data): bool
{
return DB::table($this->table)
->where($this->primaryKey, $id)
->update($data) > 0;
}
}
8.2 Plugin-Specific Repository¶
<?php
// atom-ahg-plugins/ahgLibraryPlugin/lib/Repositories/LibraryItemRepository.php
namespace ahgLibraryPlugin\Repositories;
use AtomExtensions\Repositories\BaseRepository;
use Illuminate\Support\Collection;
class LibraryItemRepository extends BaseRepository
{
protected string $table = 'atom_library_item';
public function findByCallNumber(string $callNumber): ?array
{
return DB::table($this->table)
->where('call_number', $callNumber)
->first();
}
public function getCheckedOutItems(): Collection
{
return DB::table($this->table)
->where('status', 'checked_out')
->orderBy('due_date')
->get();
}
}
9. Theme Integration¶
9.1 Bootstrap 5 Implementation¶
┌─────────────────────────────────────────────────────────────────────────┐
│ THEME ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ahgThemeB5Plugin/ │
│ ├── css/ │
│ │ ├── main.css # Bootstrap 5 core │
│ │ ├── _variables.css # CSS custom properties │
│ │ └── components/ # Component-specific styles │
│ │ │
│ ├── js/ │
│ │ ├── bootstrap.bundle.min.js │
│ │ └── ahg-theme.js # Theme JavaScript │
│ │ │
│ ├── templates/ │
│ │ ├── layout.php # Main layout (overrides core) │
│ │ └── _partials/ # Reusable components │
│ │ │
│ └── config/ │
│ └── ahgThemeB5PluginConfiguration.class.php │
│ │
│ CSS Variable Convention (BEM + Namespace) │
│ ───────────────────────────────────────── │
│ --ahg-primary: #0d6efd; │
│ --ahg-card__header--bg: var(--ahg-primary); │
│ --ahg-landing__hero--height: 400px; │
│ │
└─────────────────────────────────────────────────────────────────────────┘
10. Deployment Model¶
10.1 Installation Process¶
# Step 1: Clone repositories
git clone https://github.com/ArchiveHeritageGroup/atom-framework.git
git clone https://github.com/ArchiveHeritageGroup/atom-ahg-plugins.git
# Step 2: Install dependencies
cd atom-framework && composer install
# Step 3: Run installer
bash bin/install
# What bin/install does:
# ├── Creates symlinks for plugins
# ├── Runs database migrations (install.sql)
# ├── Copies ProjectConfiguration.class.php.template
# ├── Loads plugin data files
# ├── Enables required plugins (ahgThemeB5Plugin, ahgSecurityClearancePlugin)
# ├── Copies dist assets
# └── Clears cache
10.2 Update Process¶
# Framework updates
cd atom-framework
git pull origin main
bash bin/install --update
# Plugin updates
cd atom-ahg-plugins
git pull origin main
# Individual plugin enable
php bin/atom extension:enable ahgNewPlugin
11. Technical Specifications¶
| Component | Technology |
|---|---|
| Base Platform | AtoM 2.10 |
| PHP Version | 8.3 |
| Framework ORM | Laravel Query Builder (Illuminate\Database) |
| Core ORM | Propel (Symfony 1.x) - unchanged |
| Database | MySQL 8 |
| Search | Elasticsearch 7.10 |
| Web Server | nginx |
| Theme | Bootstrap 5 |
| CLI | Symfony Tasks + Custom Commands |
12. ProjectConfiguration.class.php.template¶
<?php
class ProjectConfiguration extends sfProjectConfiguration
{
protected $pluginsLoaded = false;
public function setup()
{
$corePlugins = array(
'sfWebBrowserPlugin',
'sfThumbnailPlugin',
'sfJSONPlugin',
'sfInstallPlugin',
'qtAccessionPlugin',
// ... other core plugins
);
$this->loadPluginsFromDatabase($corePlugins);
}
protected function loadPluginsFromDatabase($corePlugins)
{
if ($this->pluginsLoaded) {
return;
}
$frameworkPath = dirname(__FILE__) . '/../atom-framework';
if (!file_exists($frameworkPath . '/bootstrap.php')) {
$this->enablePlugins($corePlugins);
return;
}
require_once $frameworkPath . '/bootstrap.php';
try {
$dbPlugins = \Illuminate\Database\Capsule\Manager::table('atom_plugin')
->where('is_enabled', 1)
->orderBy('load_order')
->pluck('name')
->toArray();
$allPlugins = array_unique(array_merge($corePlugins, $dbPlugins));
$this->enablePlugins($allPlugins);
} catch (\Exception $e) {
$this->enablePlugins($corePlugins);
}
$this->pluginsLoaded = true;
}
}
Document Version: 1.0.0
Last Updated: January 2026
The Archive and Heritage Group