VOOZH about

URL: https://deepwiki.com/calevans/staticforge/7.1-feature-architecture

⇱ Feature Architecture | calevans/staticforge | DeepWiki


Loading...
Last indexed: 11 February 2026 (5f6a2a)
Menu

Feature Architecture

Purpose and Scope

This document describes the technical architecture of StaticForge's plugin system, referred to as "Features". It covers the discovery mechanism, class hierarchy, registration process, and lifecycle management of features. For guidance on creating custom features, see Creating Custom Features. For information about specific built-in features, see Built-in Features Overview.


Feature Discovery System

StaticForge implements a three-tier feature discovery system with explicit priority ordering. Features are loaded from multiple locations, with user-defined features taking precedence over packaged features.

Discovery Priority Hierarchy


Sources: src/Core/FeatureManager.php52-96

Feature Resolution Mechanism

The FeatureManager searches for features in specific directory structures and package definitions:

PriorityLocationType LabelOverride Behavior
1 (Highest)src/Features/*CustomOverrides library features with same name
2 (Medium)vendor/*/composer.json extra.staticforge.featureComposerLoaded after user features
3 (Lowest)vendor/eicc/staticforge/src/Features/*StandardLoaded last, can be overridden

Each feature directory must contain a Feature.php file. The class inside must implement FeatureInterface.

Sources: src/Core/FeatureManager.php52-96 src/Core/FeatureManager.php146-154

Composer Feature Discovery

For Composer packages, features are declared in composer.json:


The FeatureManager parses vendor/composer/installed.json to discover these declarations during the discoverComposerFeatures() method.

Sources: src/Core/FeatureManager.php286-327

Namespace Resolution

The discovery system attempts multiple namespace patterns when instantiating feature classes:


Sources: src/Core/FeatureManager.php269-280


Feature Interfaces and Base Classes

Class Hierarchy


Sources: src/Core/FeatureManager.php17-27 content/development/features.md20-47

FeatureInterface Contract

The FeatureInterface defines the minimal contract all features must implement:

  • getName(): string - Returns the feature's unique identifier
  • register(EventManager $eventManager, Container $container): void - Registers event listeners

Features are identified by name, enabling duplicate detection and configuration lookup in siteconfig.yaml.

Sources: src/Core/FeatureManager.php222-227

BaseFeature Abstract Class

BaseFeature provides common functionality for all features:

  • Automatic event registration from $eventListeners array
  • Container access via $this->container property
  • Logger access via $this->logger property
  • Feature-scoped storage via setFeatureVariable() and getFeatureVariable()
  • Configuration retrieval via getConfig() method

The register() method in BaseFeature iterates over the $eventListeners array and calls $eventManager->on() for each entry.

Sources: content/development/features.md26-47

BaseRendererFeature Specialization

BaseRendererFeature extends BaseFeature for content transformation features:

  • Integrates with ExtensionRegistry for file type handling
  • Provides canProcess(string $extension): bool method
  • Defines abstract processFile(Container $container, array $data): array method

Renderer features typically listen to the RENDER event and transform file content.

Sources: content/features/html-renderer.md1-62


Event Registration System

Declarative Event Listener Registration

Features declare event listeners using the $eventListeners array property:


The BaseFeature::register() method processes this array during feature registration:


Sources: content/development/features.md33-47

Event Listener Priority

Priority values control execution order when multiple features listen to the same event:

Priority RangeExecution OrderTypical Use Case
0-100FirstSystem-critical operations, file discovery modifications
100-500StandardMost feature operations
500-900LateCleanup, finalization, asset injection
900-999LastFinal processing, logging

Lower priority numbers execute first. Features can use priority to ensure dependencies execute in the correct order.

Sources: content/development/events.md113-126 content/development/architecture.md104-112

Event Listener Patterns

Common event listener patterns observed in built-in features:

Data Collection Pattern (POST_RENDER):


Data Generation Pattern (POST_LOOP):


Sources: src/Features/RssFeed/Services/RssFeedService.php31-88 src/Features/Sitemap/Services/SitemapService.php32-78


Feature Lifecycle

Lifecycle Phases


Sources: content/development/events.md31-51 content/development/architecture.md66-102

Discovery Phase

During bootstrap, FeatureManager::loadFeatures() is called to discover and instantiate all features:

  1. Check for existing features - Prevents double-loading
  2. Load disabled features list - From siteconfig.yaml disabled_features array
  3. Scan user features directory - src/Features/ (or FEATURES_DIR)
  4. Discover Composer features - Parse vendor/composer/installed.json
  5. Scan library features directory - vendor/eicc/staticforge/src/Features/
  6. Store feature metadata - In container->setVariable('features')

Sources: src/Core/FeatureManager.php52-96

Registration Phase

Each discovered feature undergoes registration:

  1. Instantiate feature class - Via namespace resolution
  2. Check disabled status - Skip if in disabled_features
  3. Check for duplicates - Skip if name already exists (higher priority wins)
  4. Call register() method - Feature registers event listeners
  5. Store in features array - $this->features[$name] = $feature
  6. Track type and status - featureTypes and featureStatuses arrays

Sources: src/Core/FeatureManager.php187-231

Execution Phase

Features execute when their registered events fire. The event system passes a $data array through all listeners in priority order:


Sources: content/development/events.md150-166

Cleanup Phase

The DESTROY event signals application shutdown. Features use this to:

  • Close file handles
  • Release resources
  • Write final logs
  • Cleanup temporary files

Sources: content/development/events.md50-51


Service-Oriented Architecture

Feature-Service Separation Pattern

Complex features delegate domain logic to service classes, keeping the Feature class focused on event handling:


Example: RssFeed Feature

The RssFeed feature delegates to RssFeedService:

  • Feature class: Registers listeners, manages lifecycle
  • RssFeedService: Collects files, generates RSS XML
  • RssBuilder: Constructs XML structure
  • FeedChannel/FeedItem: Data models

Sources: src/Features/RssFeed/Services/RssFeedService.php1-306

Service Instantiation Patterns

Services are typically instantiated in the feature's constructor or during event handling:

Constructor Injection:


Lazy Instantiation:


Sources: src/Features/RssFeed/Services/RssFeedService.php14-28 src/Features/CategoryIndex/Services/CategoryService.php14-23

Service Dependencies

Services commonly depend on:

  • Log $logger - For logging operations
  • EventManager $eventManager - For firing sub-events
  • Container variables - Retrieved via method parameters

Services should not directly access the Container. Instead, features pass required data to service methods.

Sources: src/Features/RssFeed/Services/RssFeedService.php14-28 src/Features/Sitemap/Services/SitemapService.php10-23


Configuration Integration

ConfigurableFeatureInterface

Features implementing ConfigurableFeatureInterface declare their configuration requirements:


This enables the audit:config command to validate project setup and provide meaningful error messages.

Sources: content/development/features.md96-123

Configuration Access Pattern

Features retrieve configuration from siteconfig.yaml via the getConfig() method provided by BaseFeature:


Configuration is located at siteconfig.yaml under features.{FeatureName}:


Sources: content/development/features.md96-123

Feature Disabling Mechanism

Features can be disabled via siteconfig.yaml:


The FeatureManager checks this list during discovery and skips disabled features entirely. They are not instantiated or registered.

Sources: src/Core/FeatureManager.php59-63 src/Core/FeatureManager.php209-214


Feature Status Tracking

The FeatureManager maintains three tracking structures:

ArrayPurposeValues
$featuresStores feature instancesarray<string, FeatureInterface>
$featureTypesRecords feature origin'Custom', 'Composer', 'Standard'
$featureStatusesRecords feature state'enabled', 'disabled'

These are accessible via:

  • getFeatures(): array - Returns all loaded feature instances
  • getFeature(string $name): ?FeatureInterface - Returns specific feature
  • getFeatureStatuses(): array - Returns status map

Sources: src/Core/FeatureManager.php17-39 src/Core/FeatureManager.php99-123