Files
michaelschiemer/src/Framework/Console/CommandGroupRegistry.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

294 lines
8.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Console;
use App\Framework\Discovery\Results\DiscoveryRegistry;
/**
* Registry for command groups and workflows
*/
final readonly class CommandGroupRegistry
{
private array $groups;
private array $workflows;
private array $commandGroupMapping;
public function __construct(
private DiscoveryRegistry $discoveryRegistry
) {
$this->groups = $this->loadGroups();
$this->workflows = $this->loadWorkflows();
$this->commandGroupMapping = $this->buildCommandGroupMapping();
}
/**
* Get all command groups
*/
public function getGroups(): array
{
return $this->groups;
}
/**
* Get group by name
*/
public function getGroup(string $name): ?array
{
return $this->groups[$name] ?? null;
}
/**
* Get all workflows
*/
public function getWorkflows(): array
{
return $this->workflows;
}
/**
* Get workflow by name
*/
public function getWorkflow(string $name): ?array
{
return $this->workflows[$name] ?? null;
}
/**
* Get command group mapping
*/
public function getCommandGroupMapping(): array
{
return $this->commandGroupMapping;
}
/**
* Get commands for a group
*/
public function getCommandsForGroup(string $groupName): array
{
$commands = [];
$allCommands = $this->discoveryRegistry->attributes()->get(ConsoleCommand::class);
foreach ($allCommands as $commandData) {
$commandGroup = $this->getCommandGroup($commandData);
if ($commandGroup === $groupName) {
$commands[] = $commandData;
}
}
return $commands;
}
/**
* Get organized command structure
*/
public function getOrganizedCommands(): array
{
$organized = [];
$allCommands = $this->discoveryRegistry->attributes()->get(ConsoleCommand::class);
// Group commands by their groups
foreach ($allCommands as $commandData) {
$groupName = $this->getCommandGroup($commandData);
$group = $this->getGroup($groupName);
if (! isset($organized[$groupName])) {
$organized[$groupName] = [
'name' => $groupName,
'description' => $group['description'] ?? '',
'icon' => $group['icon'] ?? $this->inferIcon($groupName),
'priority' => $group['priority'] ?? 0,
'commands' => [],
];
}
$organized[$groupName]['commands'][] = $commandData;
}
// Sort by priority
uasort($organized, fn ($a, $b) => $b['priority'] <=> $a['priority']);
// Convert to numeric array for TUI navigation (TuiState expects numeric indices)
return array_values($organized);
}
/**
* Check if command has explicit group
*/
public function hasExplicitGroup(array $commandData): bool
{
return $this->getExplicitGroup($commandData) !== null;
}
/**
* Load groups from discovery
*/
private function loadGroups(): array
{
$groups = [];
$results = $this->discoveryRegistry->attributes()->get(CommandGroup::class);
foreach ($results as $result) {
$group = $result->createAttributeInstance();
if ($group === null) {
continue;
}
$groups[$group->name] = [
'name' => $group->name,
'description' => $group->description,
'icon' => $group->icon,
'priority' => $group->priority,
'dependencies' => $group->dependencies,
'tags' => $group->tags,
'hidden' => $group->hidden,
'class' => $result->className->getFullyQualified(),
];
}
return $groups;
}
/**
* Load workflows from discovery
*/
private function loadWorkflows(): array
{
$workflows = [];
$results = $this->discoveryRegistry->attributes()->get(CommandWorkflow::class);
foreach ($results as $result) {
$workflow = $result->createAttributeInstance();
if ($workflow === null) {
continue;
}
$workflows[$workflow->name] = [
'name' => $workflow->name,
'description' => $workflow->description,
'steps' => array_map(
fn ($step) => is_array($step) ? WorkflowStep::fromArray($step) : $step,
$workflow->steps
),
'prerequisites' => $workflow->prerequisites,
'stopOnError' => $workflow->stopOnError,
'rollbackSteps' => $workflow->rollbackSteps,
'timeoutSeconds' => $workflow->timeoutSeconds,
'environment' => $workflow->environment,
'class' => $result->className->getFullyQualified(),
'method' => $result->methodName?->toString(),
];
}
return $workflows;
}
/**
* Build command to group mapping
*/
private function buildCommandGroupMapping(): array
{
$mapping = [];
$allCommands = $this->discoveryRegistry->attributes()->get(ConsoleCommand::class);
foreach ($allCommands as $commandData) {
$attribute = $commandData->createAttributeInstance();
if ($attribute === null) {
continue;
}
$commandName = $attribute->name;
$groupName = $this->getCommandGroup($commandData);
$mapping[$commandName] = $groupName;
}
return $mapping;
}
/**
* Get command group (explicit or inferred)
*/
private function getCommandGroup($commandData): string
{
// Check for explicit group attribute
$explicitGroup = $this->getExplicitGroup($commandData);
if ($explicitGroup !== null) {
return $explicitGroup;
}
// Fall back to inference - get command name from attribute instance
$attribute = $commandData->createAttributeInstance();
if ($attribute === null) {
return 'General';
}
return $this->inferCategory($attribute->name);
}
/**
* Get explicit group from command
*/
private function getExplicitGroup($commandData): ?string
{
$class = $commandData->className->getFullyQualified();
$method = $commandData->methodName?->toString();
// Check method-level group attribute
if ($method) {
$reflection = new \ReflectionMethod($class, $method);
$attributes = $reflection->getAttributes(CommandGroup::class);
if (! empty($attributes)) {
return $attributes[0]->newInstance()->name;
}
}
// Check class-level group attribute
$reflection = new \ReflectionClass($class);
$attributes = $reflection->getAttributes(CommandGroup::class);
if (! empty($attributes)) {
return $attributes[0]->newInstance()->name;
}
return null;
}
/**
* Infer category from command name (fallback)
*/
private function inferCategory(string $commandName): string
{
if (str_contains($commandName, ':')) {
return ucfirst(explode(':', $commandName)[0]);
}
return match (true) {
str_starts_with($commandName, 'test') => 'Testing',
str_starts_with($commandName, 'demo') => 'Demo',
str_starts_with($commandName, 'make') => 'Generator',
str_starts_with($commandName, 'db') => 'Database',
str_starts_with($commandName, 'mcp') => 'MCP',
default => 'General'
};
}
/**
* Infer icon for group
*/
private function inferIcon(string $groupName): string
{
return match (strtolower($groupName)) {
'testing' => '🧪',
'demo' => '🎮',
'generator' => '⚙️',
'database' => '🗄️',
'mcp' => '🤖',
'workflow' => '🔄',
'system' => '⚙️',
'security' => '🛡️',
'general' => '📂',
default => '📁'
};
}
}