Files
Jonathan Miller 4cc3f5bee4
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (pull_request) Successful in 6s
Generic: Repo Health / Scripts governance (pull_request) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 14s
Generic: Repo Health / Repository health (pull_request) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been skipped
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
style: fix all PHPCS PSR-12 violations across 74 files (7539 → 0 errors)
- Convert tabs to spaces (3,413 violations)
- Fix line endings, trailing whitespace, brace placement
- Break lines exceeding 150-char absolute limit
- Replace heredoc tab closers with spaces
- Fix empty elseif, forbidden function calls
- Update phpcs.xml: exclude rules inappropriate for CLI scripts
  (SideEffects, MissingNamespace, MultipleClasses, HeaderOrder,
  empty catch blocks)

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-24 17:07:51 -05:00

285 lines
8.7 KiB
PHP

<?php
declare(strict_types=1);
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Enterprise.Plugins
* INGROUP: MokoStandards.Enterprise
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /lib/Enterprise/PluginRegistry.php
* BRIEF: Plugin registry for available project plugins
*/
namespace MokoEnterprise;
use MokoEnterprise\Plugins\JoomlaPlugin;
use MokoEnterprise\Plugins\DolibarrPlugin;
use MokoEnterprise\Plugins\GenericPlugin;
use MokoEnterprise\Plugins\DocumentationPlugin;
use MokoEnterprise\Plugins\NodeJsPlugin;
use MokoEnterprise\Plugins\PythonPlugin;
use MokoEnterprise\Plugins\TerraformPlugin;
use MokoEnterprise\Plugins\WordPressPlugin;
use MokoEnterprise\Plugins\MobilePlugin;
use MokoEnterprise\Plugins\ApiPlugin;
use MokoEnterprise\Plugins\McpServerPlugin;
/**
* Plugin Registry - Central registry for all project type plugins
*
* Manages plugin discovery, registration, and lifecycle
*
* @package MokoStandards\Enterprise
* @version 1.0.0
*/
class PluginRegistry
{
/** @var array<string, string> Map of project types to plugin class names */
private static $pluginClasses = [
'joomla' => JoomlaPlugin::class,
'dolibarr' => DolibarrPlugin::class,
'generic' => GenericPlugin::class,
'documentation' => DocumentationPlugin::class,
'nodejs' => NodeJsPlugin::class,
'python' => PythonPlugin::class,
'terraform' => TerraformPlugin::class,
'wordpress' => WordPressPlugin::class,
'mobile' => MobilePlugin::class,
'api' => ApiPlugin::class,
'mcp-server' => McpServerPlugin::class,
];
/** @var array<string, ProjectPluginInterface> Instantiated plugins */
private static $plugins = [];
/** @var AuditLogger|null Shared audit logger */
private static $logger = null;
/** @var MetricsCollector|null Shared metrics collector */
private static $metricsCollector = null;
/**
* Set shared logger for all plugins
*
* @param AuditLogger $logger Audit logger instance
* @return void
*/
public static function setLogger(AuditLogger $logger): void
{
self::$logger = $logger;
}
/**
* Set shared metrics collector for all plugins
*
* @param MetricsCollector $metricsCollector Metrics collector instance
* @return void
*/
public static function setMetricsCollector(MetricsCollector $metricsCollector): void
{
self::$metricsCollector = $metricsCollector;
}
/**
* Register a custom plugin for a project type
*
* @param string $projectType Project type identifier
* @param string $pluginClass Fully qualified plugin class name
* @return void
* @throws \InvalidArgumentException If plugin class doesn't implement ProjectPluginInterface
*/
public static function registerPlugin(string $projectType, string $pluginClass): void
{
if (!class_exists($pluginClass)) {
throw new \InvalidArgumentException("Plugin class does not exist: {$pluginClass}");
}
if (!is_subclass_of($pluginClass, ProjectPluginInterface::class)) {
throw new \InvalidArgumentException(
"Plugin class must implement ProjectPluginInterface: {$pluginClass}"
);
}
self::$pluginClasses[$projectType] = $pluginClass;
// Clear cached instance if exists
if (isset(self::$plugins[$projectType])) {
unset(self::$plugins[$projectType]);
}
}
/**
* Get plugin instance for a project type
*
* @param string $projectType Project type identifier
* @param array $config Optional plugin configuration
* @return ProjectPluginInterface|null Plugin instance or null if not found
*/
public static function getPlugin(string $projectType, array $config = []): ?ProjectPluginInterface
{
// Check if plugin is already instantiated
if (isset(self::$plugins[$projectType])) {
return self::$plugins[$projectType];
}
// Check if plugin class is registered
if (!isset(self::$pluginClasses[$projectType])) {
return null;
}
// Instantiate plugin
$pluginClass = self::$pluginClasses[$projectType];
$plugin = new $pluginClass(self::$logger, self::$metricsCollector, $config);
// Cache plugin instance
self::$plugins[$projectType] = $plugin;
return $plugin;
}
/**
* Get all registered project types
*
* @return array List of project type identifiers
*/
public static function getRegisteredTypes(): array
{
return array_keys(self::$pluginClasses);
}
/**
* Get all registered plugins
*
* @param array $config Optional plugin configuration
* @return array<string, ProjectPluginInterface> Map of project types to plugin instances
*/
public static function getAllPlugins(array $config = []): array
{
$plugins = [];
foreach (self::$pluginClasses as $projectType => $pluginClass) {
$plugins[$projectType] = self::getPlugin($projectType, $config);
}
return $plugins;
}
/**
* Check if a plugin is registered for a project type
*
* @param string $projectType Project type identifier
* @return bool True if plugin is registered
*/
public static function hasPlugin(string $projectType): bool
{
return isset(self::$pluginClasses[$projectType]);
}
/**
* Unregister a plugin
*
* @param string $projectType Project type identifier
* @return void
*/
public static function unregisterPlugin(string $projectType): void
{
unset(self::$pluginClasses[$projectType]);
unset(self::$plugins[$projectType]);
}
/**
* Clear all plugin instances (forces re-instantiation)
*
* @return void
*/
public static function clearCache(): void
{
self::$plugins = [];
}
/**
* Get plugin information
*
* @param string $projectType Project type identifier
* @return array|null Plugin info or null if not found
*/
public static function getPluginInfo(string $projectType): ?array
{
$plugin = self::getPlugin($projectType);
if ($plugin === null) {
return null;
}
return [
'project_type' => $plugin->getProjectType(),
'plugin_name' => $plugin->getPluginName(),
'plugin_version' => $plugin->getPluginVersion(),
'required_files' => $plugin->getRequiredFiles(),
'recommended_files' => $plugin->getRecommendedFiles(),
'best_practices_count' => count($plugin->getBestPractices()),
'commands_count' => count($plugin->getCommands()),
];
}
/**
* Get all plugins information
*
* @return array Map of project types to plugin information
*/
public static function getAllPluginsInfo(): array
{
$info = [];
foreach (self::getRegisteredTypes() as $projectType) {
$info[$projectType] = self::getPluginInfo($projectType);
}
return $info;
}
/**
* Find plugin by feature/capability
*
* @param string $feature Feature name (e.g., 'package_manager', 'type_checking')
* @return array List of project types supporting the feature
*/
public static function findPluginsByFeature(string $feature): array
{
$matches = [];
foreach (self::getRegisteredTypes() as $projectType) {
$plugin = self::getPlugin($projectType);
if ($plugin !== null) {
$bestPractices = $plugin->getBestPractices();
foreach ($bestPractices as $practice) {
if (
stripos($practice['title'] ?? '', $feature) !== false ||
stripos($practice['description'] ?? '', $feature) !== false
) {
$matches[] = $projectType;
break;
}
}
}
}
return $matches;
}
/**
* Get plugin registry statistics
*
* @return array Registry statistics
*/
public static function getStatistics(): array
{
return [
'total_plugins' => count(self::$pluginClasses),
'instantiated_plugins' => count(self::$plugins),
'registered_types' => self::getRegisteredTypes(),
'has_logger' => self::$logger !== null,
'has_metrics_collector' => self::$metricsCollector !== null,
];
}
}